Python argparse: 至少需要一个参数

我一直在使用 argparse编写一个 Python 程序,它可以是 -process-upload或者两者兼而有之:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('-process', action='store_true')
parser.add_argument('-upload',  action='store_true')
args = parser.parse_args()

如果没有至少一个参数,程序就没有意义。如何配置 argparse以强制至少选择一个参数?

UPDATE:

在评论之后: Python 用什么方法来参数化一个至少有一个选项的程序?

67378 次浏览
if not (args.process or args.upload):
parser.error('No action requested, add -process or -upload')

如果没有“或者两者都有”的部分(我最初没有看到这一点) ,你可以使用这样的东西:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('--process', action='store_const', const='process', dest='mode')
parser.add_argument('--upload',  action='store_const', const='upload', dest='mode')
args = parser.parse_args()
if not args.mode:
parser.error("One of --process or --upload must be given")

尽管如此,使用 子命令可能是一个更好的主意。

args = vars(parser.parse_args())
if not any(args.values()):
parser.error('No arguments provided.')

如果您需要一个至少有一个参数的 python 程序来运行,那么添加一个 没有具有选项前缀(默认情况下是-或——)的参数,并设置 nargs=+(至少需要一个参数)。我发现这个方法的问题是,如果不指定参数,argparse 将生成一个“参数太少”错误,并且不会打印出帮助菜单。如果你不需要这个功能,以下是如何在代码中实现它:

import argparse


parser = argparse.ArgumentParser(description='Your program description')
parser.add_argument('command', nargs="+", help='describe what a command is')
args = parser.parse_args()

当您添加一个带有选项前缀的参数时,nargs 将控制整个参数解析器,而不仅仅是选项。(我的意思是,如果您有一个 --option标志与 nargs="+",那么 --option标志期望至少有一个参数。如果你有 optionnargs="+",它希望至少有一个参数。)

需求检讨

  • 使用 argparse(我将忽略这一个)
  • allow one or two actions to be called (at least one required).
  • 我更愿意称之为“ POSIX”-like

生活在命令行上也有一些隐含的要求:

  • explain the usage to the user in a way which is easy to understand
  • 选择权是可选的
  • 允许指定标志和选项
  • 允许与其他参数(如文件名或名称)组合。

使用 docopt的示例解决方案(文件 managelog.py) :

"""Manage logfiles
Usage:
managelog.py [options] process -- <logfile>...
managelog.py [options] upload -- <logfile>...
managelog.py [options] process upload -- <logfile>...
managelog.py -h


Options:
-V, --verbose      Be verbose
-U, --user <user>  Username
-P, --pswd <pswd>  Password


Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
from docopt import docopt
args = docopt(__doc__)
print args

试着运行它:

$ python managelog.py
Usage:
managelog.py [options] process -- <logfile>...
managelog.py [options] upload -- <logfile>...
managelog.py [options] process upload -- <logfile>...
managelog.py -h

Show the help:

$ python managelog.py -h
Manage logfiles
Usage:
managelog.py [options] process -- <logfile>...
managelog.py [options] upload -- <logfile>...
managelog.py [options] process upload -- <logfile>...
managelog.py -h


Options:
-V, --verbose      Be verbose
-U, --user <user>  Username
-P, --pswd <pswd>  P    managelog.py [options] upload -- <logfile>...


Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>

使用它:

$ python managelog.py -V -U user -P secret upload -- alfa.log beta.log
{'--': True,
'--pswd': 'secret',
'--user': 'user',
'--verbose': True,
'-h': False,
'<logfile>': ['alfa.log', 'beta.log'],
'process': False,
'upload': True}

简称 short.py

甚至还有更短的变体:

"""Manage logfiles
Usage:
short.py [options] (process|upload)... -- <logfile>...
short.py -h


Options:
-V, --verbose      Be verbose
-U, --user <user>  Username
-P, --pswd <pswd>  Password


Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
from docopt import docopt
args = docopt(__doc__)
print args

用法如下:

$ python short.py -V process upload  -- alfa.log beta.log
{'--': True,
'--pswd': None,
'--user': None,
'--verbose': True,
'-h': False,
'<logfile>': ['alfa.log', 'beta.log'],
'process': 1,
'upload': 1}

注意,不是布尔值的“进程”和“上传”键有计数器。

It turns out, we cannot prevent duplication of these words:

$ python short.py -V process process upload  -- alfa.log beta.log
{'--': True,
'--pswd': None,
'--user': None,
'--verbose': True,
'-h': False,
'<logfile>': ['alfa.log', 'beta.log'],
'process': 2,
'upload': 1}

结论

设计好的命令行界面有时候是很有挑战性的。

基于命令行的程序有多个方面:

  • 良好的命令行设计
  • 选择/使用合适的解析器

argparse提供了很多,但是限制了可能的场景,并且可能变得非常复杂。

使用 docopt可以缩短时间,同时保持可读性并提供高度的灵活性。如果您设法从 dictionary 中获取解析参数,并进行一些转换(到整数) ,则打开文件。.)手动(或由其他库称为 schema) ,您可能会发现 docopt非常适合命令行解析。

对操作列表使用 append _ const,然后检查该列表是否已填充:

parser.add_argument('-process', dest=actions, const="process", action='append_const')
parser.add_argument('-upload',  dest=actions, const="upload", action='append_const')


args = parser.parse_args()


if(args.actions == None):
parser.error('Error: No actions requested')

您甚至可以直接在常量中指定方法。

def upload:
...


parser.add_argument('-upload',  dest=actions, const=upload, action='append_const')
args = parser.parse_args()


if(args.actions == None):
parser.error('Error: No actions requested')


else:
for action in args.actions:
action()

对于 http://bugs.python.org/issue11588,我正在探索将 mutually_exclusive_group概念推广到处理类似情况的方法。

有了这个开发 argparse.py,< a href = “ https://github.com/hpaulj/argparse _ questions/blob/nested/argparse.py”rel = “ norefrer”> https://github.com/hpaulj/argparse_issues/blob/nested/argparse.py 我可以写:

parser = argparse.ArgumentParser(prog='PROG',
description='Log archiver arguments.')
group = parser.add_usage_group(kind='any', required=True,
title='possible actions (at least one is required)')
group.add_argument('-p', '--process', action='store_true')
group.add_argument('-u', '--upload',  action='store_true')
args = parser.parse_args()
print(args)

产生以下 help:

usage: PROG [-h] (-p | -u)


Log archiver arguments.


optional arguments:
-h, --help     show this help message and exit


possible actions (at least one is required):
-p, --process
-u, --upload

它接受“-u”、“-up”、“-proc-up”等输入。

它最终运行了一个类似于 https://stackoverflow.com/a/6723066/901925的测试,尽管错误信息需要更加清晰:

usage: PROG [-h] (-p | -u)
PROG: error: some of the arguments process upload is required

我想知道:

  • 参数 kind='any', required=True是否足够清楚(接受任何组,至少需要一个) ?

  • 用法 (-p | -u)清楚吗? 一个必需的互斥 _ 组产生相同的结果。有其他的表示法吗?

  • 使用这样的小组比使用 phihag's简单测试更直观吗?

我知道这个方法很古老,但是要求一个选项但是禁止多个选项(XOR)的方法是这样的:

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-process', action='store_true')
group.add_argument('-upload',  action='store_true')
args = parser.parse_args()
print args

产出:

>opt.py
usage: multiplot.py [-h] (-process | -upload)
multiplot.py: error: one of the arguments -process -upload is required


>opt.py -upload
Namespace(process=False, upload=True)


>opt.py -process
Namespace(process=True, upload=False)


>opt.py -upload -process
usage: multiplot.py [-h] (-process | -upload)
multiplot.py: error: argument -process: not allowed with argument -upload

最好的方法是使用 python 内置模块 Add _ 欲加之欲加之欲加之欲加之欲加之欲

parser = argparse.ArgumentParser(description='Log archiver arguments.')
group = parser.add_mutually_exclusive_group()
group.add_argument('-process', action='store_true')
group.add_argument('-upload',  action='store_true')
args = parser.parse_args()

如果您只希望通过命令行选择一个参数,那么只需要使用义务 = True 作为 group 的参数

group = parser.add_mutually_exclusive_group(required=True)

也许可以使用子解析器?

import argparse


parser = argparse.ArgumentParser(description='Log archiver arguments.')
subparsers = parser.add_subparsers(dest='subparser_name', help='sub-command help')
parser_process = subparsers.add_parser('process', help='Process logs')
parser_upload = subparsers.add_parser('upload', help='Upload logs')
args = parser.parse_args()


print("Subparser: ", args.subparser_name)

现在 --help显示:

$ python /tmp/aaa.py --help
usage: aaa.py [-h] {process,upload} ...


Log archiver arguments.


positional arguments:
{process,upload}  sub-command help
process         Process logs
upload          Upload logs


optional arguments:
-h, --help        show this help message and exit
$ python /tmp/aaa.py
usage: aaa.py [-h] {process,upload} ...
aaa.py: error: too few arguments
$ python3 /tmp/aaa.py upload
Subparser:  upload

还可以向这些子解析器添加其他选项。此外,您还可以将函数绑定到给定的子命令上直接调用(参见文档) ,而不是使用 dest='subparser_name'

这就达到了目的,这也将反映在 argparse 自动生成的 --help输出中,这是大多数理智的程序员都想要的结果(也可以使用可选参数) :

parser.add_argument(
'commands',
nargs='+',                      # require at least 1
choices=['process', 'upload'],  # restrict the choice
help='commands to execute'
)

官方文件: Https://docs.python.org/3/library/argparse.html#choices

吸毒

    parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('-process', action='store_true')
parser.add_argument('-upload',  action='store_true')
args = parser.parse_args()

也许你可以试试:

    if len([False for arg in vars(args) if vars(args)[arg]]) == 0:
parsers.print_help()
exit(-1)

至少这是我刚才使用的,希望这对将来的某人有所帮助!

比如

parser.add_argument("--a")
parser.add_argument("--b")

我们可以使用以下方法

if not args.a and not args.b:
parser.error("One of --a or --b must be present")