包装需要大量参数的子进程调用的 Pythonic 方法?
Pythonic way to wrap a subprocess call that takes a lot of parameters?
我正在编写一个 python 脚本,它提供了一个更加用户友好的 API 命令行工具。一些必要的命令调用需要很多参数(有时多达 10 个左右),但这在 Python 中不是好的做法。它们不能只是默认值;必须能够为给定调用设置所有参数。
我现在的结构是一个APIclass,里面有expose_image()等函数,然后是一个接口class来处理子进程命令的构造和电话。我看不到添加更多 classes 会有帮助,因为 API class 仍然必须以某种方式生成和传递参数。
我想出的一个解决方案是用参数填充字典或 namedtuple 并将其作为 **kwargs 传递,这使事情看起来更好一些,但不那么明确。
是否有更好的处理方法?
谢谢!
值得称赞的是,您希望为此命令构建一个 Pythonic API 而不仅仅是 API。
我不确定您为什么忽略默认参数?如果默认值为 None
,您可以将其视为不向命令行添加内容的指南。
例如,假设您要调用树命令。你可以有类似的东西:
def my_tree(dirs_only=False, full_prefix=False, max_level=None, pattern=None):
cmd_line = ['tree']
if dirs_only:
cmd_line.append('-d')
if full_prefix:
cmd_line.append('-f')
if max_level is not None:
cmd_line.append('-L')
cmd_line.append(str(max_level))
if pattern is not None:
cmd_line.append('-P')
cmd_line.append(pattern)
subprocess.do_something_with(cmd_line)
my_tree
的调用者可以像在 shell 中那样与其交互:
my_tree()
my_tree(dirs_only=True)
my_tree(pattern='Foo*')
my_tree(pattern='Foo*', max_level=2, full_prefix=True)
在 Java、C# 或 Dart 等语言中,您经常会看到 "fluent" API,也许这些可能会有所帮助。它会产生如下代码:
my_tree().call()
my_tree().dirs_only().call()
my_tree().with_pattern('Foo*').call()
my_tree() \
.with_pattern('Foo*') \
.with_max_level(2) \
.full_prefix() \
.call()
虽然调用看起来更漂亮,但您需要编写很多样板文件才能获得所说的漂亮,这肯定感觉有点不符合 Pythonic。
就像你说的,**
的 kvargs 是 向你的函数传递 多个参数的便捷方式,但是最好在函数定义中显式声明参数:
def store(data, database,
user, password,
host=DEFAULT_HOST,
port=PG_DEFAULT_PORT,
chunk_size=64,
flags=None):
pass
# call
params = {"data": generate_data(),
"database": "mydb",
"user": "guest",
"password": "guest",
"chunk_size": 128
}
store(**params)
另一种方法是使用 "Parameters" class,像这样(来自 pika
库的示例):
class ConnectionParameters(Parameters):
def __init__(self,
host=None,
port=None,
virtual_host=None,
credentials=None,
channel_max=None,
frame_max=None,
heartbeat_interval=None,
ssl=None,
ssl_options=None,
connection_attempts=None,
retry_delay=None,
socket_timeout=None,
locale=None,
backpressure_detection=None):
super(ConnectionParameters, self).__init__()
# Create the default credentials object
if not credentials:
credentials = self._credentials(self.DEFAULT_USERNAME,
self.DEFAULT_PASSWORD)
...
# call
conn_params = pika.ConnectionParameters(host=self._host,
port=self._port,
credentials=cred)
conn = pika.BlockingConnection(parameters=conn_params)
我正在编写一个 python 脚本,它提供了一个更加用户友好的 API 命令行工具。一些必要的命令调用需要很多参数(有时多达 10 个左右),但这在 Python 中不是好的做法。它们不能只是默认值;必须能够为给定调用设置所有参数。
我现在的结构是一个APIclass,里面有expose_image()等函数,然后是一个接口class来处理子进程命令的构造和电话。我看不到添加更多 classes 会有帮助,因为 API class 仍然必须以某种方式生成和传递参数。
我想出的一个解决方案是用参数填充字典或 namedtuple 并将其作为 **kwargs 传递,这使事情看起来更好一些,但不那么明确。
是否有更好的处理方法?
谢谢!
值得称赞的是,您希望为此命令构建一个 Pythonic API 而不仅仅是 API。
我不确定您为什么忽略默认参数?如果默认值为 None
,您可以将其视为不向命令行添加内容的指南。
例如,假设您要调用树命令。你可以有类似的东西:
def my_tree(dirs_only=False, full_prefix=False, max_level=None, pattern=None):
cmd_line = ['tree']
if dirs_only:
cmd_line.append('-d')
if full_prefix:
cmd_line.append('-f')
if max_level is not None:
cmd_line.append('-L')
cmd_line.append(str(max_level))
if pattern is not None:
cmd_line.append('-P')
cmd_line.append(pattern)
subprocess.do_something_with(cmd_line)
my_tree
的调用者可以像在 shell 中那样与其交互:
my_tree()
my_tree(dirs_only=True)
my_tree(pattern='Foo*')
my_tree(pattern='Foo*', max_level=2, full_prefix=True)
在 Java、C# 或 Dart 等语言中,您经常会看到 "fluent" API,也许这些可能会有所帮助。它会产生如下代码:
my_tree().call()
my_tree().dirs_only().call()
my_tree().with_pattern('Foo*').call()
my_tree() \
.with_pattern('Foo*') \
.with_max_level(2) \
.full_prefix() \
.call()
虽然调用看起来更漂亮,但您需要编写很多样板文件才能获得所说的漂亮,这肯定感觉有点不符合 Pythonic。
就像你说的,**
的 kvargs 是 向你的函数传递 多个参数的便捷方式,但是最好在函数定义中显式声明参数:
def store(data, database,
user, password,
host=DEFAULT_HOST,
port=PG_DEFAULT_PORT,
chunk_size=64,
flags=None):
pass
# call
params = {"data": generate_data(),
"database": "mydb",
"user": "guest",
"password": "guest",
"chunk_size": 128
}
store(**params)
另一种方法是使用 "Parameters" class,像这样(来自 pika
库的示例):
class ConnectionParameters(Parameters):
def __init__(self,
host=None,
port=None,
virtual_host=None,
credentials=None,
channel_max=None,
frame_max=None,
heartbeat_interval=None,
ssl=None,
ssl_options=None,
connection_attempts=None,
retry_delay=None,
socket_timeout=None,
locale=None,
backpressure_detection=None):
super(ConnectionParameters, self).__init__()
# Create the default credentials object
if not credentials:
credentials = self._credentials(self.DEFAULT_USERNAME,
self.DEFAULT_PASSWORD)
...
# call
conn_params = pika.ConnectionParameters(host=self._host,
port=self._port,
credentials=cred)
conn = pika.BlockingConnection(parameters=conn_params)