简体   繁体   中英

How to make optional subparser in python3?

I want to input args that not configured in argparse:

parser = argparse.ArgumentParser(prog='PROG')

subparsers = parser.add_subparsers(help='sub-command help', dest="character", required=False)
subparsers.required = False

base_subparser = argparse.ArgumentParser(add_help=False)
# define common shared arguments
base_subparser.add_argument('--disable', choices=['false', 'true'])
base_subparser.add_argument('--foo', choices=['false', 'true'])
# create the parser for the "a" command
parser_a = subparsers.add_parser('a', help='a help', parents=[base_subparser])
parser_a.add_argument('--bar', choices='ABC', help='bar help')

# create the parser for the "b" command
parser_b = subparsers.add_parser('b', help='b help', parents=[base_subparser])
parser_b.add_argument('--baz', choices='XYZ', help='baz help')

argcomplete.autocomplete(parser)
args = parser.parse_known_args()
print(args)

I use the parse_known_args() which use a list to store the args not configured in argparse. However, when I use ./prog.py key = val , it shows argument character: invalid choice: 'key=val' (choose from 'a', 'b') . So I have to choose 'a' or 'b', how can I input the args not configured in argparse without choose one of the subparsers.

The error you see is the same as produced by a '?'positional with choices:

In [25]: import argparse

In [26]: parser = argparse.ArgumentParser() 
In [27]: parser.add_argument('foo', nargs='?', choices=['a','b'])

'foo' is optional:

In [28]: parser.parse_known_args([])
Out[28]: (Namespace(foo=None), [])

In [29]: parser.parse_known_args(['a'])
Out[29]: (Namespace(foo='a'), [])

but any string is parsed as a possible 'foo' value:

In [30]: parser.parse_known_args(['c'])
usage: ipykernel_launcher.py [-h] [{a,b}]
ipykernel_launcher.py: error: argument foo: invalid choice: 'c' (choose from 'a', 'b')

providing a proper choice first, allows it to treat 'c' as an extra:

In [31]: parser.parse_known_args(['a','c'])
Out[31]: (Namespace(foo='a'), ['c'])

Or if the string looks like a optional's flag:

In [32]: parser.parse_known_args(['-c'])
Out[32]: (Namespace(foo=None), ['-c'])

Another possibility is to go ahead and name a subparser, possibly a dummy one, and provide the extra. The subparser will be the one that actually puts that string in the 'unknowns' category.

In [40]: parser = argparse.ArgumentParser()
In [41]: subp = parser.add_subparsers(dest='cmd')
In [44]: p1 = subp.add_parser('a')

In [45]: parser.parse_known_args(['a','c'])
Out[45]: (Namespace(cmd='a'), ['c'])

In [46]: parser.parse_known_args([])       # not-required is the default
Out[46]: (Namespace(cmd=None), [])

Keep in mind that the main parser does not "know" anything about subparsers, except is a positional. It's doing its normal allocating strings to actions. But once it calls a subparser, that parser has full control over the parsing. Once it's done it passes the namespace back to the main, but the main doesn't do any more parsing - it just wraps things up and exits (with results or error).


Since subp is a positional with a special Action subclass, _SubParsersAction , I was thinking it might be possible to create a flagged argument with that class

parser.add_argument('--foo', action=argparse._SubParsersAction)

but there's more going on in add_subparsers , so it isn't a trivial addition. This is a purely speculative idea.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM