Python3 argparse set_defaults 不将字符串作为选项名称?

Python3 argparse set_defaults doesn't take string as option name?

我是 argparse 的新手,所以这可能是基础。

我更喜欢将所有字符串常量定义一次 (blah = 'foo'),然后在整个代码中使用它。当我到达 set_defaults 时,似乎我仅限于 kwarg 类型参数。

也就是说,parser.set_defaults(NUM=ONE) 不会将 NUM 视为字符串。这是一个更完整的示例:

ONE = 'one'
TWO = 'two'
SIX = 'six'
NUMBER_OPTS = [ONE, TWO, SIX]
NUM = 'num'

parser = argparse.ArgumentParser()
pform = parser.add_mutually_exclusive_group()
for opt in NUMBER_OPTS:
    pform.add_argument('--'+opt, dest=NUM, action='store_const', const=opt)
parser.set_defaults(NUM=ONE) # Can't find a syntax to make this DWIM
args = parser.parse_args()
print("%s is %s" % (NUM, vars(args)[NUM]))

因此,虽然 add_argument 将字符串作为目标,但 set_defaults 不会。

可以使用字典扩展:

parser.set_defaults(**{NUM: ONE})

您可以使用 argparsedefault 参数来做到这一点:

pform.add_argument('--'+opt, dest=NUM, action='store_const', const=opt, default=NUM)

运行 您的代码稍作修改:

In [35]: ONE = 'one'
    ...: TWO = 'two'
    ...: SIX = 'six'
    ...: NUMBER_OPTS = [ONE, TWO, SIX]
    ...: NUM = 'num'
    ...: alist = []
    ...: parser = argparse.ArgumentParser()
    ...: pform = parser.add_mutually_exclusive_group()
    ...: for opt in NUMBER_OPTS:
    ...:     a = pform.add_argument('--'+opt, dest=NUM, action='store_const', co
    ...: nst=opt)
    ...:     alist.append(a)

In [37]: alist
Out[37]: 
[_StoreConstAction(option_strings=['--one'], dest='num', nargs=0, const='one', default=None, type=None, choices=None, help=None, metavar=None),
 _StoreConstAction(option_strings=['--two'], dest='num', nargs=0, const='two', default=None, type=None, choices=None, help=None, metavar=None),
 _StoreConstAction(option_strings=['--six'], dest='num', nargs=0, const='six', default=None, type=None, choices=None, help=None, metavar=None)]

alist 包含指向由 add_argument 语句创建的 3 个 Action 对象的指针。我本可以从 pform._group_actions 获得相同的列表,因为这些操作已添加到该组。

通过显式设置 dest,它是 'num' 而不是 opt(从长标志派生):

In [40]: alist[0].dest
Out[40]: 'num'

parser.set_defaults(num=ONE) 将带有 dest='one' 的操作的 default 属性设置为`'one'.

In [45]: [a.default for a in alist]
Out[45]: ['one', 'one', 'one']
In [46]: [a.const for a in alist]
Out[46]: ['one', 'two', 'six']

这个默认值也可以在循环中定义:

pform.add_argument('--'+opt, dest=NUM, default=ONE, const=opt, , action='store_const')

对于列表中的第一个 default 也足够了,而其他的则为默认值 None。这是在解析开始时如何设置默认值的结果。

我可以验证:

In [47]: alist[1].default=TWO
In [48]: alist[2].default=SIX
In [49]: parser.parse_args([])
Out[49]: Namespace(num='one')

const 按预期工作:

In [50]: parser.parse_args(['--two'])
Out[50]: Namespace(num='two')

可以通过多种方式获取已解析命名空间中的 num 值:

In [51]: _.num                   # as attribute
Out[51]: 'two'
In [52]: getattr(Out[50],'num')   # NUM works here
Out[52]: 'two'
In [53]: vars(Out[50])['num']     # dictionary, NUM works here
Out[53]: 'two'

_是之前的答案,也存储在Out列表中。)

另一个技巧是在解析之前定义一个命名空间对象。在那里定义的任何值都优先于默认值。请注意,Namespace(...) 定义与 set_defaults:

具有相同的语法
In [54]: ns = argparse.Namespace(num='four')
In [55]: parser.parse_args([], namespace=ns)
Out[55]: Namespace(num='four')
In [56]: parser.parse_args(['--six'], namespace=ns)
Out[56]: Namespace(num='six')

Python 和大多数语言一样,区分符号和字符串。仅引用字符串。符号可用于命名变量、对象属性和函数关键字。

在你的例子中 NUM 是一个符号,'num' 是一个字符串。在dest=NUM中,dest是符号,NUM中的值是字符串值。在alist[0].dest中,dest是属性名,它的值是字符串'num'.

但是 argparse 获取 a.dest 值,并使用它来定义 args 命名空间中的属性。这就是为什么 num 可以用作下面的属性名称:

In [58]: args = parser.parse_args(['--six'])
In [59]: args
Out[59]: Namespace(num='six')
In [60]: args.num
Out[60]: 'six'

argparse实际上使用getattrsetattr来读取和设置这些属性的值:

In [61]: getattr(args, 'num')
Out[61]: 'six'
In [62]: getattr(args, alist[0].dest)
Out[62]: 'six'

这对 dest 值施加了一些假设;它们甚至不必是有效的属性名称。

我在评论中指出,字典也可以用符号和字符串来定义:

In [63]: {'num':'one'}
Out[63]: {'num': 'one'}
In [64]: dict(num='one')
Out[64]: {'num': 'one'}

关键字参数,包括开放式 **kwargs,也跨越 symbol/string 边界:

In [65]: def foo(**kwargs):
    ...:     print(kwargs)
    ...:     
In [66]: foo(num='one')    # keyword input
{'num': 'one'}
In [67]: foo(**Out[63])    # expand a dictionary
{'num': 'one'}

所以虽然Python区分了符号和字符串,但它有多种跨越边界的方式。


NUM=ONE 运行,但定义了一个 args.NUM 属性。

In [72]: parser.set_defaults(NUM=ONE)
In [73]: parser.parse_args([])
Out[73]: Namespace(NUM='one', num='one')
In [74]: parser.parse_args([]).NUM
Out[74]: 'one'

现在让我们摆脱那个NUM默认值

In [76]: parser._defaults
Out[76]: {'NUM': 'one', 'num': 'one'}
In [78]: del parser._defaults['NUM']
In [79]: parser.parse_args([])
Out[79]: Namespace(num='one')

def set_defaults(self, **kwargs):
    # original method, takes keyword-value or **dict
    self._defaults.update(kwargs)

    # if these defaults match any existing arguments, replace
    # the previous default on the object with the new one
    for action in self._actions:
        if action.dest in kwargs:
            action.default = kwargs[action.dest]

def set_defaults(parser, adict):
    # function version that takes a dictionary rather than **kwargs
    parser._defaults.update(adict)
    for action in parser._actions:
        if action.dest in adict:
            action.default = adict[action.dest]

用于

In [80]: set_defaults(parser, {NUM: 'three'})  # diff value
In [81]: parser.parse_args([])
Out[81]: Namespace(num='three')

诚然,这并没有太大变化;只是消除了 ** 扩展。 Python 中没有办法使 (...NUM=ONE...) 表示 (... 'num':ONE...).

也许这整本词典业务是一个红鲱鱼。我们可以改为传递 dest 和 default

的元组
def set_defaults(parser, alist):
    # function version that takes a list of tuples
    for dest, default in alist: # iterate on tuples
        for action in parser._actions:
            if action.dest == dest:
                action.default = default

In [83]: set_defaults(parser, [(NUM, SIX)])
In [84]: parser.parse_args([])
Out[84]: Namespace(num='six')

换句话说,我们只需要一种将 dest 值与 default 值配对的方法。它不一定是字典 key:value 映射。