【问题标题】:Support global arguments before or after sub-command in argparse支持 argparse 中子命令之前或之后的全局参数
【发布时间】:2020-07-15 09:50:05
【问题描述】:

使用解析器will allow for sharing common arguments between parsers(例如parents and sub-commands)设置parents 参数。但是,当使用带有dest 关键字的参数时,将基本解析器应用于父命令和子命令似乎会用来自子命令解析器的值覆盖来自父解析器的值,调用是否在子命令中指定了参数。

如何使用argparse module 合并父命令和子命令中的选项(即,如果任一解析器包含该选项,则存储该值,如果两个解析器均未指定该选项,则使用默认值,没关系如果两个解析器都指定了选项怎么处理)?

sample.py:

from argparse import ArgumentParser
parser = ArgumentParser(add_help=False) # The "base"
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true')
parser.add_argument('-d', '--dir', dest='dir', default=None)

parser_main = ArgumentParser(parents=[parser])
subparsers = parser_main.add_subparsers(dest='command')
subparsers.add_parser('cmd1', parents=[parser])
args = parser_main.parse_args()

print(str(args))

然后,在 shell 中:

> sample.py -v -d abc
Namespace(command=None, dir='abc', verbose=True)
> sample.py -v cmd1 -d abc
Namespace(command='cmd1', dir='abc', verbose=False)
> sample.py -d abc cmd1 -v
Namespace(command='cmd1', dir=None, verbose=True)
> sample.py cmd1 -v -d abc
Namespace(command='cmd1', dir='abc', verbose=True)

【问题讨论】:

  • 由于子解析器namespace 是如何用于更新父命名空间的,它的值(包括任何默认值)会破坏相应的父属性。 (我可以向您指出几年前实现此功能的补丁。)无论如何,如果您不希望父 dest 被覆盖,它们应该是不同的。
  • argparse._SubParsersAction.__call__ 其中for key, value in vars(subnamespace).items(): setattr(namespace, key, value) 进行更新。

标签: python argparse


【解决方案1】:

使用SUPPRESS 作为子解析器默认值可以防止它覆盖父解析器值。 SUPPRESS 默认值不会在解析开始时插入到 namespace 中。仅当用户使用该参数时才写入值。

import argparse    
parser = argparse.ArgumentParser()
parser.add_argument('-f', '--foo', default='foobar')
parser.add_argument('-v', '--verbose', action='store_const', default=False, const=True)

sp = parser.add_subparsers(dest='cmd')
sp1 = sp.add_parser('cmd1')
sp1.add_argument('-f', '--foo', default=argparse.SUPPRESS)
sp1.add_argument('-v', '--verbose', action='store_const', default=argparse.SUPPRESS, const=True)

args = parser.parse_args()
print(args)

样本运行:

1833:~/mypy$ python3 stack62904585.py
Namespace(cmd=None, foo='foobar', verbose=False)
1834:~/mypy$ python3 stack62904585.py --foo FOO -v
Namespace(cmd=None, foo='FOO', verbose=True)
1834:~/mypy$ python3 stack62904585.py cmd1 
Namespace(cmd='cmd1', foo='foobar', verbose=False)
1834:~/mypy$ python3 stack62904585.py -v cmd1 -f bar
Namespace(cmd='cmd1', foo='bar', verbose=True)

最后改变此行为的补丁 (2014)

https://bugs.python.org/issue9351 argparse set_defaults on subcommands 应该覆盖顶级 set_defaults

还有https://bugs.python.org/issue27859

【讨论】:

【解决方案2】:

您可以通过使用 dest 关键字指定不同的名称来将值存储在不同的属性中:

from argparse import ArgumentParser
parser = ArgumentParser(add_help=False) # The "base"
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true')
parser.add_argument('-d', '--dir', dest='dir', default=None)

parser_main = ArgumentParser()
parser_main.add_argument('-v', '--verbose', dest='g_verbose', action='store_true')
parser_main.add_argument('-d', '--dir', dest='g_dir', default=None)
subparsers = parser_main.add_subparsers(dest='command')
subparsers.add_parser('cmd1', parents=[parser])
args = parser_main.parse_args()

verbose = args.verbose or args.g_verbose if hasattr(args, 'verbose') else args.g_verbose
d = (args.g_dir if args.dir is None else args.dir) if hasattr(args, 'dir') else args.g_dir

【讨论】:

  • 我真的希望这不是解决方案。我发布这个答案只是为了抢占其他可能认为这是一个合理的解决方法的人。
猜你喜欢
  • 2014-06-11
  • 1970-01-01
  • 2020-11-10
  • 2017-03-28
  • 1970-01-01
  • 1970-01-01
  • 2017-10-13
  • 1970-01-01
  • 2012-01-29
相关资源
最近更新 更多