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)
如果你问我,那就很整洁。
答案 0 :(得分:2)
您是否尝试过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
答案 1 :(得分:2)
类似plac
的功能由argh提供,其特别功能是简单创建子分段(如git
或django-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_signature
,args
的{{1}}参数会扩展为关键字参数:
load
答案 2 :(得分:1)
您可以使用inspect
模块查看自己的函数定义。这样你至少可以编写一个基本的argparse
骨架。但是,您可能需要更多信息,而不仅仅是参数名称和可能的默认值。
例如,您还需要提供说明。您可以通过以适当的格式创建文档字符串来提供此信息。有文档字符串的解析器(例如Sphynx)使用这些额外信息,我认为您将能够自动为您的函数生成argparse调用。
我认为不需要装饰器,因为可能所有信息都可以存储在你的文档字符串中。
让我知道您的表现,我对您项目的结果感兴趣。
答案 3 :(得分:1)
另一个有趣的替代方案是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
答案 4 :(得分:1)
最少的样板"我发现的库是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