如何定义一组两个互斥的位置参数?
我想用 argparse
来写一些代码,让它可以有两种用法:
./tester.py all
./tester.py name someprocess
也就是说,要么指定 all
,要么指定 name
以及一些额外的字符串。
我尝试这样实现:
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('all', action='store_true', \
help = "Stops all processes")
group.add_argument('name', \
help = "Stops the named process")
print parser.parse_args()
但结果给我报了错。
ValueError: mutually exclusive arguments must be optional
有没有什么好的方法可以正确实现?我还想避免使用子解析器。
5 个回答
我同意这看起来确实像是一个子解析器的问题。如果你不想通过使用 --all
和 --name
来让它成为一个可选参数,我有一个建议,就是完全忽略 all
和 name
,然后使用以下的逻辑:
- 如果直接运行
tester.py
而不带任何参数,就停止所有进程。 - 如果运行
tester.py
时带了一些参数,就只停止那些指定的进程。
这可以通过以下方式实现:
import argparse, sys
parser = argparse.ArgumentParser()
parser.add_argument('processes', nargs='*')
parsed = parser.parse(sys.argv[1:])
print parsed
它的行为将如下所示:
$ python tester.py Namespace(processes=[]) $ python tester.py proc1 Namespace(processes=['proc1'])
或者,如果你坚持使用自己的语法,你可以创建一个自定义类。实际上,你并不是在处理一个“互斥组”的情况,因为我假设如果指定了 all
,你会忽略其他参数(即使 name
是其他参数之一),而当指定了 name
时,后面的任何内容都会被视为进程的名称。
import argparse
import sys
class AllOrName(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if len(values)==0:
raise argparse.ArgumentError(self, 'too few arguments')
if values[0]=='all':
setattr(namespace, 'all', True)
elif values[0]=='name':
if len(values)==1:
raise argparse.ArgumentError(self, 'please specify at least one process name')
setattr(namespace, 'name', values[1:])
else:
raise argparse.ArgumentError(self, 'only "all" or "name" should be specified')
parser = argparse.ArgumentParser()
parser.add_argument('processes', nargs='*', action=AllOrName)
parsed = parser.parse_args(sys.argv[1:])
print parsed
其行为如下:
$ python argparse_test.py name usage: argparse_test.py [-h] [processes [processes ...]] argparse_test.py: error: argument processes: please specify at least one process name $ python argparse_test.py name proc1 Namespace(name=['proc1'], processes=None) $ python argparse_test.py all Namespace(all=True, processes=None) $ python argparse_test.py host usage: argparse_test.py [-h] [processes [processes ...]] argparse_test.py: error: argument processes: only "all" or "name" should be specified $ python argparse_test.py usage: argparse_test.py [-h] [processes [processes ...]] argparse_test.py: error: argument processes: too few arguments
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-a','--all', action='store_true', \
help = "Stops all processes")
group.add_argument('-n','--name', \
help = "Stops the named process")
print parser.parse_args()
usage: zx.py [-h] (-a | -n NAME)
optional arguments:
-h, --help show this help message and exit
-a, --all Stops all processes
-n NAME, --name NAME Stops the named process
在命令行中输入 ./tester.py -h
这个指令,意思是你想要查看一个叫做 tester.py 的程序的帮助信息。这个程序可能是用 Python 写的,前面的 ./ 表示这个程序在当前文件夹里。-h 通常是用来请求帮助的选项,运行后会显示一些使用说明和可用的命令选项。
这个问题已经有一年了,不过因为所有的回答都建议用不同的语法,我就给出一个更接近提问者原意的解释。
首先,提问者代码中的问题:
一个位置参数的 store_true
是没有意义的(虽然允许这样写)。它不需要任何参数,所以它总是 True
。如果你给它一个 'all',就会出现 error: unrecognized arguments: all
的错误。
另一个参数只接受一个值,并把这个值赋给 name
属性。它不接受额外的 process
值。
关于 mutually_exclusive_group
,这个错误信息在 parse_args
之前就会出现。要让这样的组有意义,所有的选项都必须是可选的。这意味着要么有一个 --
标志,要么是一个位置参数,且 nargs
要等于 ?
或 *
。而且在这个组里不应该有多个这样的位置信息。
使用 --all
和 --name
的最简单替代方案,可以是这样的:
p=argparse.ArgumentParser()
p.add_argument('mode', choices=['all','name'])
p.add_argument('process',nargs='?')
def foo(args):
if args.mode == 'all' and args.process:
pass # can ignore the process value or raise a error
if args.mode == 'name' and args.process is None:
p.error('name mode requires a process')
args = p.parse_args()
foo(args) # now test the namespace for correct `process` argument.
接受的命名空间看起来会是:
Namespace(mode='name', process='process1')
Namespace(mode='all', process=None)
choices
模拟了子解析器参数的行为。在 parse_args
之后自己进行测试,通常比让 argparse
做一些特殊的事情要简单。