Support global arguments before or after sub-command in argparse

给你一囗甜甜゛ 提交于 2021-02-05 09:37:19

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!