python argparse - 带选择的可选追加参数

20 投票
5 回答
26224 浏览
提问于 2025-04-17 08:27

我有一个脚本,里面会询问用户要执行的一系列预定义的操作。我还想在用户没有提供任何操作时,能够使用一份特定的操作列表。然而,似乎同时做到这两点是很困难的。

当用户没有提供任何参数时,他们会收到一个错误提示,说明默认选择是无效的。

acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', action='append', choices=acts, default=[['dump', 'clear']])
args = p.parse_args([])
>>> usage: [-h] [{clear,copy,dump,lock} [{clear,copy,dump,lock} ...]]
: error: argument action: invalid choice: [['dump', 'clear']] (choose from 'clear', 'copy', 'dump', 'lock')

而当用户定义了一组操作时,结果的命名空间会把用户的操作添加到默认操作后面,而不是替换掉默认操作。

acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', action='append', choices=acts, default=[['dump', 'clear']])
args = p.parse_args(['lock'])
args
>>> Namespace(action=[['dump', 'clear'], ['dump']])

5 个回答

6

我最后做了以下几件事:

  • 没有使用 append 方法
  • 把空列表加入到可能的选择中,不然空输入会导致程序出错
  • 没有设置默认值
  • 之后检查一下列表是否为空,如果是的话再设置实际的默认值

示例:

parser = argparse.ArgumentParser()
parser.add_argument(
    'is',
    type=int,
    choices=[[], 1, 2, 3],
    nargs='*',
)

args = parser.parse_args(['1', '3'])
assert args.a == [1, 3]

args = parser.parse_args([])
assert args.a == []
if args.a == []:
    args.a = [1, 2]

args = parser.parse_args(['1', '4'])
# Error: '4' is not valid.
6

在文档中(http://docs.python.org/dev/library/argparse.html#default),提到:

对于位置参数,如果nargs等于?或*,当没有提供命令行参数时,会使用默认值。

那么,如果我们这样做:

acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', choices=acts, default='clear')    
print p.parse_args([])

我们得到了预期的结果

Namespace(action='clear')

问题出现在你把一个列表作为默认值的时候。不过我在文档里见过这个,

parser.add_argument('bar', nargs='*', default=[1, 2, 3], help='BAR!')

所以,我也不知道 :-(

无论如何,这里有一个解决方法,可以实现你想要的效果:

import sys, argparse
acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', choices=acts)
args = ['dump', 'clear'] # I set the default here ... 
if sys.argv[1:]:
    args = p.parse_args()
print args
17

你需要的可以通过自定义一个 argparse.Action 来实现,下面是一个例子:

import argparse

parser = argparse.ArgumentParser()

class DefaultListAction(argparse.Action):
    CHOICES = ['clear','copy','dump','lock']
    def __call__(self, parser, namespace, values, option_string=None):
        if values:
            for value in values:
                if value not in self.CHOICES:
                    message = ("invalid choice: {0!r} (choose from {1})"
                               .format(value,
                                       ', '.join([repr(action)
                                                  for action in self.CHOICES])))

                    raise argparse.ArgumentError(self, message)
            setattr(namespace, self.dest, values)

parser.add_argument('actions', nargs='*', action=DefaultListAction,
                    default = ['dump', 'clear'],
                    metavar='ACTION')

print parser.parse_args([])
print parser.parse_args(['lock'])

这个脚本的输出结果是:

$ python test.py 
Namespace(actions=['dump', 'clear'])
Namespace(actions=['lock'])

撰写回答