argparse optional subparser (for --version)

2019-01-17 16:17发布

I have the following code (using Python 2.7):

# shared command line options, like --version or --verbose
parser_shared = argparse.ArgumentParser(add_help=False)
parser_shared.add_argument('--version', action='store_true')

# the main parser, inherits from `parser_shared`
parser = argparse.ArgumentParser(description='main', parents=[parser_shared])

# several subcommands, which can't inherit from the main parser, since
# it would expect subcommands ad infinitum
subparsers = parser.add_subparsers('db', parents=[parser_shared])

...

args = parser.parse_args()

Now I would like to be able to call this program e.g. with the --version appended to the normal program or some subcommand:

$ prog --version
0.1

$ prog db --version
0.1

Basically, I need to declare optional subparsers. I'm aware that this isn't really supported, but are there any workarounds or alternatives?

Edit: The error message I am getting:

$ prog db --version
# works fine

$ prog --version
usage: ....
prog: error: too few arguments

7条回答
老娘就宠你
2楼-- · 2019-01-17 16:30

As discussed in http://bugs.python.org/issue9253 (argparse: optional subparsers), as of Python 3.3, subparsers are now optional. This was an unintended result of a change in how parse_args checked for required arguments.

I found a fudge that restores the previous (required subparsers) behavior, explicitly setting the required attribute of the subparsers action.

parser = ArgumentParser(prog='test')
subparsers = parser.add_subparsers()
subparsers.required = True   # the fudge
subparsers.dest = 'command'
subparser = subparsers.add_parser("foo", help="run foo")
parser.parse_args()

See that issue for more details. I expect that if and when this issue gets properly patched, subparsers will be required by default, with some sort of option to set its required attribute to False. But there is a big backlog of argparse patches.

查看更多
冷血范
3楼-- · 2019-01-17 16:34

FWIW, I ran into this also, and ended up "solving" it by not using subparsers (I already had my own system for printing help, so didn't lose anything there).

Instead, I do this:

parser.add_argument("command", nargs="?",
                    help="name of command to execute")

args, subcommand_args = parser.parse_known_args()

...and then the subcommand creates its own parser (similar to a subparser) which operates only on subcommand_args.

查看更多
Root(大扎)
4楼-- · 2019-01-17 16:36

According to documentation, --version with action='version' (and not with action='store_true') prints automatically the version number:

parser.add_argument('--version', action='version', version='%(prog)s 2.0')
查看更多
ゆ 、 Hurt°
5楼-- · 2019-01-17 16:38

Yeah, I just checked svn, which is used as an object example in the add_subparsers() documentation, and it only supports '--version' on the main command:

python zacharyyoung$ svn log --version
Subcommand 'log' doesn't accept option '--version'
Type 'svn help log' for usage.

Still:

# create common parser
parent_parser = argparse.ArgumentParser('parent', add_help=False)
parent_parser.add_argument('--version', action='version', version='%(prog)s 2.0')

# create the top-level parser
parser = argparse.ArgumentParser(parents=[parent_parser])
subparsers = parser.add_subparsers()

# create the parser for the "foo" command
parser_foo = subparsers.add_parser('foo', parents=[parent_parser])

Which yields:

python zacharyyoung$ ./arg-test.py --version
arg-test.py 2.0
python zacharyyoung$ ./arg-test.py foo --version
arg-test.py foo 2.0
查看更多
6楼-- · 2019-01-17 16:40

This seems to implement the basic idea of an optional subparser. We parse the standard arguments that apply to all subcommands. Then, if anything is left, we invoke the parser on the rest. The primary arguments are a parent of the subcommand so the -h appears correctly. I plan to enter an interactive prompt if no subcommands are present.

import argparse

p1 = argparse.ArgumentParser( add_help = False )    
p1.add_argument( ‘–flag1′ )

p2 = argparse.ArgumentParser( parents = [ p1 ] )
s = p2.add_subparsers()
p = s.add_parser( ‘group’ )
p.set_defaults( group=True )

( init_ns, remaining ) = p1.parse_known_args( )

if remaining:
    p2.parse_args( args = remaining, namespace=init_ns )
else:
    print( ‘Enter interactive loop’ )

print( init_ns )
查看更多
Evening l夕情丶
7楼-- · 2019-01-17 16:43

While we wait for this feature to be delivered, we can use code like this:

# Make sure that main is the default sub-parser
if '-h' not in sys.argv and '--help' not in sys.argv:
    if len(sys.argv) < 2:
        sys.argv.append('main')
    if sys.argv[1] not in ('main', 'test'):
        sys.argv = [sys.argv[0], 'main'] + sys.argv[1:]
查看更多
登录 后发表回答