【问题标题】:argparse: How can I allow multiple values to override a defaultargparse:如何允许多个值覆盖默认值
【发布时间】:2012-09-09 18:30:52
【问题描述】:

这与question about allowing an argument to be specified multiple times 中涉及的主题略有相关。

我希望能够像这样多次指定一个选项:

 tool --foo 1 --foo 2 --foo 3

还有这样的:

 tool a b c

我也想同时支持两者:

 tool a b c --foo 1 --foo2 --foo 3

这适用于:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('foo', nargs='*', action='append')
parser.add_argument('--foo', nargs='*', dest='foo', action='append')

结果列表可以很容易地展平:

args = parser.parse_args('a b c --foo 1 --foo 2 --foo 3'.split())
args.foo = [el for elements in args.foo for el in elements]

产量:

>>> args
Namespace(foo=['a', 'b', 'c', '1', '2', '3'])

如何在用户指定一个参数后立即不使用默认值的方式添加默认值?

如果只将default=[['spam']] 添加到add_argument() 调用之一,则默认值始终是结果的一部分。一旦用户自己提供参数,我就无法让 argparse 自行删除它。

我希望argparse 已经提供了一个解决方案。

【问题讨论】:

  • 迟到:虽然我理解将其全部保存在 argparse 内的动机,但对于大多数应用程序来说,一个简单的 if len(foo) > 1: foo.pop(0) 不会解决问题吗?
  • @hBy2Py:理解你的观点(是的),不,我想把它放在图书馆里;-)。我经常是一个理想主义者,为了获得乐趣和学习而超越“足够好”。对于实际提议的代码,它可能是if len(foo) == 0: foo.append(default)(并且在代码中没有指定默认值)。否则可能会出现奇怪的角落情况(订单不保证,不知道?还有什么?)。有趣的是,在 mgilson 回答之前,我自己按照您在 Action 的子类中的建议写了一个答案。我删除了它,因为接受的解决方案对我来说效果很好并且更简洁。

标签: python python-3.x default-value argparse


【解决方案1】:

我认为这是对另一个答案的更清晰的变体(依赖于自定义操作的 self.default 属性):

import argparse
import sys

class Extender(argparse.Action):
    def __call__(self,parser,namespace,values,option_strings=None):
        #Need None here incase `argparse.SUPPRESS` was supplied for `dest`
        dest = getattr(namespace,self.dest,None) 
        #print dest,self.default,values,option_strings
        if(not hasattr(dest,'extend') or dest == self.default):
            dest = []
            setattr(namespace,self.dest,dest)
            #if default isn't set to None, this method might be called
            # with the default as `values` for other arguements which
            # share this destination.
            parser.set_defaults(**{self.dest:None}) 

        try:
            dest.extend(values)
        except ValueError:
            dest.append(values)

        #another option:
        #if not isinstance(values,basestring):
        #    dest.extend(values)
        #else:
        #    dest.append(values) #It's a string.  Oops.

def new_parser(args):
    parser = argparse.ArgumentParser()
    parser.add_argument('foo', nargs='*',action=Extender)
    parser.add_argument('--foo', nargs='*', dest='foo', action=Extender)
    parser.set_defaults(foo = [['spam']])
    return parser.parse_args(args.split())

tests = {'a b c --foo 1 --foo 2 --foo 3':['a','b','c','1','2','3'],
         '':[['spam']],
         'a b c --foo 1 2 3':['a','b','c','1','2','3'],
         '--foo 1':['1'],
         'a':['a']}

for s,r in tests.items():
    print ( "parsing: {0}".format(s) )
    args = new_parser(s)
    if(args.foo != r):
        print ("ERROR")
        print (args.foo)
        print (r)
        sys.exit(1)
    print ( args )
    print ('*'*80)

另请注意,我使用parser.set_defaults(...) 设置foo 属性的默认值。

【讨论】:

  • 我正在为这个细节而苦苦挣扎。如果add_argument() 中没有指定默认值,第一个getattr 将引发异常。另外,当该属性从未设置时,为什么还要检查hasattr(dest,'extend')
  • @cfi -- 我相信argparse 为命名空间提供默认值None,如果它不存在的话,虽然你可以随时使用dest = getattr(namespace,self.dest,None),如果你真的想要.接下来,我检查extend 以确保该对象是list(或类似列表)。如果它是 None (尚未通过默认设置),那么我们需要创建一个列表(然后在 try/except 块中使用。
  • 啊。嗯。如果用户错误地指定例如一个字符串默认值,然后在处理第一个参数的过程中,它会被一个空列表静默覆盖,而不是引发异常。
  • @cfi -- 我稍微改动了一下。我将None 添加到getattr,因为argparse.SUPPRESS 会导致您描述的异常。
  • 这是令人讨厌的行为:我做了traceback.print_stacktrace() 并看到 if 位置有任何默认值(即使使用parser.set_defaults(),并且用户只调用该工具使用可选风格,然后argparse 安排通过consume_positionals 对我们的__call__ 方法的最终调用,默认值在values。无法与用户arg 的调用区分开来。删除解析器默认值是好主意!比污染 arg 结果命名空间要好得多。您的解决方案仍然对位置 add_argument() 中的默认值很敏感。没关系。
猜你喜欢
  • 2022-01-25
  • 1970-01-01
  • 1970-01-01
  • 2011-12-20
  • 1970-01-01
  • 2022-08-05
  • 2019-03-20
  • 2013-02-03
相关资源
最近更新 更多