如何定义一组两个互斥的位置参数?

15 投票
5 回答
11729 浏览
提问于 2025-04-17 19:46

我想用 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 个回答

0

我同意这看起来确实像是一个子解析器的问题。如果你不想通过使用 --all--name 来让它成为一个可选参数,我有一个建议,就是完全忽略 allname,然后使用以下的逻辑:

  1. 如果直接运行 tester.py 而不带任何参数,就停止所有进程。
  2. 如果运行 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
1
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 通常是用来请求帮助的选项,运行后会显示一些使用说明和可用的命令选项。

15

这个问题已经有一年了,不过因为所有的回答都建议用不同的语法,我就给出一个更接近提问者原意的解释。

首先,提问者代码中的问题:

一个位置参数的 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 做一些特殊的事情要简单。

撰写回答