(半)自动生成函数的argparsers

geo*_*org 5 python decorator argparse

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

我有一堆要公开给命令行的函数。因此,基本上,一个模块:

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

 if __name__ == '__main__':
     argparse stuff
Run Code Online (Sandbox Code Playgroud)

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

 python commands.py move spam ham
 python commands.py unlink --parrot Polly
Run Code Online (Sandbox Code Playgroud)

尽管实现起来非常简单,但是涉及很多布线:

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')
...
Run Code Online (Sandbox Code Playgroud)

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

如果这些函数可以为自己提供argparse的东西,那就更好了,这样主代码应该是这样的:

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

copy.register(subparsers)
move.register(subparsers)
unlink.register(subparsers)
...
Run Code Online (Sandbox Code Playgroud)

我想到了以下几点:

@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):
    ...
Run Code Online (Sandbox Code Playgroud)

我的问题:

  • 是否有一个类似这样的图书馆?
  • 如果没有,是否可以编写这样的装饰器,怎么做?
  • 还有其他/更好的方法来实现我想要的吗?

更新:

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'
Run Code Online (Sandbox Code Playgroud)

这是主要模块:

import plac
import cmds

plac.call(cmds)
Run Code Online (Sandbox Code Playgroud)

如果你问我的话,很整洁。

iMo*_*om0 3

你试过plac吗?

文档中的示例:

# 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)
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)