问题
I would like to build a (complicated) command-line argument parser using argparse module facilities. The 'main' script can accept sub-commands, and some of the sub-commands also have their own sub-commands. Here is a MWE:
#!/usr/bin/env python3
import argparse
def arg_parser():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(required=True, dest="cmd")
p = subparsers.add_parser("c1", help="Command 1")
p.add_argument("foo", help="foo argument")
p.add_argument("bar", help="bar argument")
p.add_argument("-z", "--baz", help="baz argument")
p = subparsers.add_parser("c2", help="Command 2")
q = p.add_subparsers(required=True, dest="sub_command")
r = q.add_parser("s1", help="Command 1 - Sub-command 1")
r.add_argument("arg1", help="first argument")
r.add_argument("-a", "--arg2", help="second argument")
r = q.add_parser("s2", help="Command 1 - Sub-command 2")
return parser
def main():
args = arg_parser().parse_args()
print(args)
if __name__ == "__main__":
main()
So far everything works well, and the help messages generated by argparse look correct:
$ ./main.py
usage: main.py [-h] {c1,c2} ...
positional arguments:
{c1,c2}
c1 Command 1
c2 Command 2
optional arguments:
-h, --help show this help message and exit
$ ./main.py c2 -h
usage: main.py c2 [-h] {s1,s2} ...
positional arguments:
{s1,s2}
s1 Command 1 - Sub-command 1
s2 Command 1 - Sub-command 2
optional arguments:
-h, --help show this help message and exit
$ ./main.py c2 s1 -h
usage: main.py c2 s1 [-h] [-a ARG2] arg1
positional arguments:
arg1 first argument
optional arguments:
-h, --help show this help message and exit
-a ARG2, --arg2 ARG2 second argument
Now I would like to split the long arg_parser routine into sub-functions that create the parser as follows:
def sc1_parser():
r = argparse.ArgumentParser(add_help=False)
r.add_argument("arg1", help="first argument")
r.add_argument("-a", "--arg2", help="second argument")
return r
def c1_parser():
parser = argparse.ArgumentParser(add_help=None)
parser.add_argument("foo", help="foo argument")
parser.add_argument("bar", help="bar argument")
parser.add_argument("-z", "--baz", help="baz argument")
return parser
def c2_parser():
r = argparse.ArgumentParser(add_help=False)
q = r.add_subparsers(required=True, dest="sub_command")
q.add_parser("s1", help="Command 1 - Sub-command 1",
parents=[sc1_parser()])
q.add_parser("s2", help="Command 1 - Sub-command 2")
return r
def top_parser():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(required=True, dest="cmd")
subparsers.add_parser("c1", help="Command 1", parents=[c1_parser()])
subparsers.add_parser("c2", help="Command 2", parents=[c2_parser()])
return parser
(and obviously replace the call to 'arg_parser()' by 'top_parser()').
In this case, however, the help messages for sub-commands 's1' and 's2' are incorrect:
$ ./main.py -h
usage: main.py [-h] {c1,c2} ...
positional arguments:
{c1,c2}
c1 Command 1
c2 Command 2
optional arguments:
-h, --help show this help message and exit
$ ./main.py c2 -h
usage: main.py c2 [-h] {s1,s2} ...
positional arguments:
{s1,s2}
s1 Command 1 - Sub-command 1
s2 Command 1 - Sub-command 2
optional arguments:
-h, --help show this help message and exit
$ ./main.py c2 s1 -h
usage: main.py s1 [-h] [-a ARG2] arg1
positional arguments:
arg1 first argument
optional arguments:
-h, --help show this help message and exit
-a ARG2, --arg2 ARG2 second argument
$ ./main.py c2 s2 -h
usage: main.py s2 [-h]
optional arguments:
-h, --help show this help message and exit
Any idea why this is happening?
My guess is that in the first version (arg_parse() routine) the sub-command parser (the parser for s1 and s2) is added to the a parser returned from add_parser method of a sub-parser. But In the second case they are added to a fresh 'ArgumentParser'.
回答1:
If I add to your first script (before the return):
print("progs: ")
print("main: ", parser.prog)
print(" p: ", p.prog)
print(" r: ", r.prog)
a run is:
1319:~/mypy$ python3 stack63361458_0.py c2 s2 -h
progs:
main: stack63361458_0.py
p: stack63361458_0.py c2
r: stack63361458_0.py c2 s2
usage: stack63361458_0.py c2 s2 [-h]
optional arguments:
-h, --help show this help message and exit
usage includes the prog. If you don't provide it, argparse derives it from sys.argv[0], and for subparsers, adds on some strings to show the subparser's name (and required main arguments). Code for that addition is in add_subparses and add_parser.
Adding similar prints to the top_parser case
1349:~/mypy$ python3 stack63361458_0.py c2 s2 -h
s2 prog: stack63361458_0.py s2
top prog: stack63361458_0.py
c2 prog: stack63361458_0.py c2
usage: stack63361458_0.py s2 [-h]
optional arguments:
-h, --help show this help message and exit
c2 gets the expected prog, but s2.prog is created without "knowledge" that it will be called by the c2 subparser. Using parents bypasses that link.
But I could supply my own prog for the sub-sub-parser:
s2 = q.add_parser("s2", help="Command 1 - Sub-command 2", prog=f'MyProg {sys.argv[0]} c2 s2 ')
1358:~/mypy$ python3 stack63361458_0.py c2 s2 -h
s2 prog: MyProg stack63361458_0.py c2 s2
top prog: stack63361458_0.py
c2 prog: stack63361458_0.py c2
usage: MyProg stack63361458_0.py c2 s2 [-h]
optional arguments:
-h, --help show this help message and exit
We can also supply a custom usage.
Instead of the parents mechanism which creates s2 before c2, we could create c2 and then pass that to the function that creates its arguments:
def c2_parser(q):
r = q.add_parser("s1", help="Command 1 - Sub-command 1")
r.add_argument("arg1", help="first argument")
r.add_argument("-a", "--arg2", help="second argument")
r = q.add_parser("s2", help="Command 1 - Sub-command 2")
and in main:
p = subparsers.add_parser("c2", help="Command 2")
c2_parser(p)
parents are (somewhat) useful as a means of adding the same arguments to multiple subparsers, but aren't necessary when just refactoring larger builds.
来源:https://stackoverflow.com/questions/63361458/python-argparse-creates-incorrect-usage-string-when-using-nested-sub-parsers