Pythonic方式包装需要大量参数的子进程调用?

时间:2016-09-23 08:21:32

标签: python subprocess parameter-passing args kwargs

我正在编写一个python脚本,它为命令行工具提供了更加用户友好的API。一些必要的命令调用需要很多参数(有时最多约10个),但这不是Python的好习惯。它们不能只是默认值;必须能够为给定的呼叫设置所有参数。

我当前的结构是一个API类,它具有expose_image()等函数,然后是一个接口类来处理子进程命令和调用的构造。我没有看到添加更多类会有所帮助,因为API类仍然必须以某种方式生成和传递参数。

我提出的一个解决方案是使用参数填充字典或namedtuple并将其作为** kwargs传递,这使得事情看起来更好,但不那么明确。

有没有更好的方法来解决这个问题?

谢谢!

2 个答案:

答案 0 :(得分:2)

值得称道的是,您希望构建一个Pythonic API而不仅仅是此命令的API。

我不确定你为什么忽略默认参数?如果默认值为None,您可以将其视为不向命令行添加内容的指南。

例如,假设您要调用tree命令。你可以有类似的东西:

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等语言中,您经常会看到"流利的" 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。

答案 1 :(得分:2)

就像你说的那样,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)

另一种方法是使用"参数" 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)