【问题标题】:(semi-) automatic generation of argparsers for functions(半)自动生成函数的 argparsers
【发布时间】:2012-10-26 06:08:12
【问题描述】:

tldnr:给定一个函数,有没有办法根据其签名自动创建 ArgumentParser?

我有一堆函数想要在命令行中公开。所以基本上,一个模块:

 def copy(foo, bar, baz):
    ...
 def move(from, to):
    ...
 def unlink(parrot, nomore=True):
    ...

 if __name__ == '__main__':
     argparse stuff

可以像这样从命令行调用:

 python commands.py move spam ham
 python commands.py unlink --parrot Polly

虽然这很容易实现,但涉及到很多布线:

parser = argparse.ArgumentParser(...)
subparsers = parser.add_subparsers()
...
c = subparsers.add_parser('unlink', description='Unlink a parrot')
c.add_argument('--parrot', help='parrots name', required=True)
c.add_argument('--nomore', help='this parrot is no more', action='store_true')
...
c = subparsers.add_parser('move', description='Move stuff')
...

等等,对于每个函数。最糟糕的是,如果函数参数发生变化(并且确实发生了变化),argparse 的内容需要手动同步。

如果函数可以为自己提供 argparse 的东西会更好,这样主代码就会像这样:

parser = argparse.ArgumentParser(...)
subparsers = parser.add_subparsers()

copy.register(subparsers)
move.register(subparsers)
unlink.register(subparsers)
...

我想到了以下几点:

@args(
    description='Unlink a parrot',
    parrot={'required':True, 'help':'parrots name'},
    nomore={'action': 'store_true', 'help': 'this parrot is no more'}
)
def unlink(parrot, nomore=True):
    ...

我的问题:

  • 有没有这样的库?
  • 如果没有,是否可以编写这样的装饰器,如何编写?
  • 还有其他/更好的方法来实现我想要的吗?

更新:

plac 似乎是解决方案。下面是如何用 plac 做我想做的事:

命令模块:cmds.py:

import plac

@plac.annotations(
    foo=('the foo thing'),
    bar=('the bar thing'),
    fast=('do a fast copy', 'flag')
)
def copy(foo, bar, fast=False):
    """Copy some foo to bar."""
    pass
        
@plac.annotations(
    parrots=('parrots names'),
    nomore=('these parrots are no more', 'flag'),
    repeat=('repeat n times', 'option', 'r', int)
)
def unlink(nomore=False, repeat=1, *parrots):
    """Unlink some parrots."""
    pass

#more commands...

# export commands so that plac knows about them
commands = 'copy', 'unlink'

这是主模块:

import plac
import cmds

plac.call(cmds)

如果你问我,那就太好了。

【问题讨论】:

  • 您可能必须在函数中添加注释。那么应该可以编写一个给定函数注释的函数返回该函数的解析器。

标签: python decorator argparse


【解决方案1】:

你试过plac吗?

docs中的一个例子:

# dbcli.py
import plac
from sqlalchemy.ext.sqlsoup import SqlSoup

@plac.annotations(
    db=plac.Annotation("Connection string", type=SqlSoup),
    header=plac.Annotation("Header", 'flag', 'H'),
    sqlcmd=plac.Annotation("SQL command", 'option', 'c', str, metavar="SQL"),
    delimiter=plac.Annotation("Column separator", 'option', 'd'),
    scripts=plac.Annotation("SQL scripts"),
    )
def main(db, header, sqlcmd, delimiter="|", *scripts):
    "A script to run queries and SQL scripts on a database"
    yield 'Working on %s' % db.bind.url

    if sqlcmd:
        result = db.bind.execute(sqlcmd)
        if header: # print the header
            yield delimiter.join(result.keys())
        for row in result: # print the rows
            yield delimiter.join(map(str, row))

    for script in scripts:
        db.bind.execute(open(script).read())
        yield 'executed %s' % script

if __name__ == '__main__':
    for output in plac.call(main):
        print(output)

输出:

usage: dbcli.py [-h] [-H] [-c SQL] [-d |] db [scripts [scripts ...]]

A script to run queries and SQL scripts on a database

positional arguments:
  db                    Connection string
  scripts               SQL scripts

optional arguments:
  -h, --help            show this help message and exit
  -H, --header          Header
  -c SQL, --sqlcmd SQL  SQL command
  -d |, --delimiter |   Column separator

【讨论】:

    【解决方案2】:

    类似于plac 的功能由argh 提供,特别是可以简单地创建子解析器(如gitdjango-admin.py 中的那些)。

    来自其docs的示例:

    from argh import *
    
    def dump(args):
        return db.find()
    
    @command
    def load(path, format='json'):
        print loaders[format].load(path)
    
    p = ArghParser()
    p.add_commands([load, dump])
    
    if __name__ == '__main__':
        p.dispatch()
    

    产生以下--help 响应:

    usage: prog.py [-h] {load,dump} ...
    
    positional arguments:
      {load,dump}
        load
        dump
    
    optional arguments:
      -h, --help   show this help message and exit
    

    以下是load --help:

    usage: prog.py load [-h] [-f FORMAT] path
    
    positional arguments:
      path
    
    optional arguments:
      -h, --help            show this help message and exit
      -f FORMAT, --format FORMAT
    

    参数可以被注释:

    @arg('path')
    @arg('--format', choices=['yaml','json'], default='json')
    @arg('--dry-run', default=False)
    @arg('-v', '--verbosity', choices=range(0,3), default=1)
    def load(args, LOADERS={'json': json.load, 'yaml': yaml.load}):
        loader = loaders[args.format]
        data = loader(open(args.path))
        ...
    

    使用@plain_signatureloadargs 参数被扩展为关键字参数:

    @arg('path')
    @arg('--format', choices=['yaml','json'], default='json')
    @arg('--dry-run', default=False)
    @arg('-v', '--verbosity', choices=range(0,3), default=1)
    @plain_signature
    def load(path, format, dry_run, verbosity):
        ...
    

    【讨论】:

    • 谢谢,这看起来很有趣。在 plac 文档中还有一个 similar projects 列表。
    • 似乎argh also has such a list 显然......而且它更长:P
    • Argh 现在也支持“自然”语法,即您编写普通的 Python 函数,剩下的就是 Argh 的事了。
    【解决方案3】:

    我发现的“最少样板”库是fire (pip install fire)。

    为您的示例创建命令行解析器非常简单:

    import fire
    
    def copy(foo, bar, baz):
    ...
    def unlink(parrot, nomore=True):
    ...
    
    if __name__ == '__main__':
        fire.Fire()
    

    这会将您的模块变成“Fire” CLI:

    python your_module.py copy sim sala bim
    

    【讨论】:

      【解决方案4】:

      您可以使用inspect 模块查看您自己的函数定义。这样你至少可以编写一个基本的argparse 骨架。但是,您可能需要更多信息,而不仅仅是参数名称和可能的默认值。

      例如,您还需要提供描述。您可以通过以适当格式创建文档字符串来提供这些信息。有用于文档字符串的解析器(例如Sphynx)使用这些额外信息,我认为您将能够为您的函数自动生成 argparse 调用。

      我认为不需要装饰器,因为可能所有信息都可以存储在您的文档字符串中。

      让我知道你的情况,我对你项目的结果很感兴趣。

      【讨论】:

      • 谢谢!解析文档字符串听起来很诱人,但我想我会暂时坚持使用 plac(请参阅更新)。
      【解决方案5】:

      另一个有趣的替代方案是commando python 模块作为带有附加实用程序的 argparse 的声明性接口。

      示例

      没有突击队:

      def main():
          parser = argparse.ArgumentParser(description='hyde - a python static website generator',
                                        epilog='Use %(prog)s {command} -h to get help on individual commands')
          parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + __version__)
          parser.add_argument('-s', '--sitepath', action='store', default='.', help="Location of the hyde site")
          subcommands = parser.add_subparsers(title="Hyde commands",
                                           description="Entry points for hyde")
          init_command = subcommands.add_parser('init', help='Create a new hyde site')
          init_command.set_defaults(run=init)
          init_command.add_argument('-t', '--template', action='store', default='basic', dest='template',
                           help='Overwrite the current site if it exists')
          init_command.add_argument('-f', '--force', action='store_true', default=False, dest='force',
                           help='Overwrite the current site if it exists')
          args = parser.parse_args()
          args.run(args)
      
      def init(self, params):
          print params.sitepath
          print params.template
          print params.overwrite
      

      与突击队:

      class Engine(Application):
      
          @command(description='hyde - a python static website generator',
                  epilog='Use %(prog)s {command} -h to get help on individual commands')
          @param('-v', '--version', action='version', version='%(prog)s ' + __version__)
          @param('-s', '--sitepath', action='store', default='.', help="Location of the hyde site")
          def main(self, params): pass
      
          @subcommand('init', help='Create a new hyde site')
          @param('-t', '--template', action='store', default='basic', dest='template',
                  help='Overwrite the current site if it exists')
          @param('-f', '--force', action='store_true', default=False, dest='overwrite',
                  help='Overwrite the current site if it exists')
          def init(self, params):
              print params.sitepath
              print params.template
              print params.overwrite
      

      【讨论】:

      • 谢谢,看起来很有趣!
      猜你喜欢
      • 2013-10-07
      • 2010-09-29
      • 1970-01-01
      • 2011-08-21
      • 2016-09-07
      • 1970-01-01
      • 2018-12-04
      • 2016-12-07
      • 1970-01-01
      相关资源
      最近更新 更多