问题
Setting the parents argument with a parser will allow for sharing common arguments between parsers (e.g. parents and sub-commands). But, applying a base parser to both the parent and sub-command appears to overwrite the value from the parent parser with the value from the sub-command parser when using an argument that has specified the value attribute to with a dest
keyword, whether or not the invocation has specified the argument in the sub-command.
How can I use argparse module to merge the options in the parent and the sub-command (i.e. store the value if either parser contains the option, use the default if neither parser specifies the option, and it doesn't matter how to handle if both parsers specify the option)?
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))
Then, in the 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)
回答1:
Using SUPPRESS
for the subparser default keeps it from overwriting the parent parser value. A SUPPRESS
default is not inserted into the namespace
at the start of parsing. A value is written only if the user used that argument.
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)
sample runs:
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)
The patch that last changed this behavior (2014)
https://bugs.python.org/issue9351 argparse set_defaults on subcommands should override top level set_defaults
also https://bugs.python.org/issue27859
回答2:
You can store the value in different attributes by specifying different names with the dest
keyword:
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
来源:https://stackoverflow.com/questions/62904585/support-global-arguments-before-or-after-sub-command-in-argparse