When using argparse, some subcommands need the same options and I'm using parents
to avoid repeatedly defining them in every sub-command.
script filename: testarg.py
import argparse
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('-H', '--host', default='192.168.122.1')
parser.add_argument('-P', '--port', default='12345')
subparsers = parser.add_subparsers()
# subcommand a
parser_a = subparsers.add_parser('a', parents=[parser])
parser_a.add_argument('-D', '--daemon', action='store_true')
parser_a.add_argument('-L', '--log', default='/tmp/test.log')
# subcommand b
parser_b = subparsers.add_parser('b', parents=[parser])
parser_b.add_argument('-D', '--daemon', action='store_true')
# subcommand c
parser_c = subparsers.add_parser('c', parents=[parser])
args = parser.parse_args()
print args
But when I run command:
>>>./testarg.py a
usage: testarg.py a [-h] [-H HOST] [-P PORT] [-D] [-L LOG] {a,b,c} ...
testarg.py a: error: too few arguments
expecting output:
>>>./testarg.py a
Namespace(daemon=False, host='192.168.122.1', log='/tmp/test.log', port='12345')
>>>./testarg.py b -H 127.0.0.1 -P 11111
Namespace(daemon=False, host='127.0.0.1', port='11111')
>>>./testarg.py c
Namespace(host='192.168.122.1', port='12345')
also,
>>>./testarg.py c -H 127.0.0.1 -P 12222
Namespace(host='127.0.0.1', port='12222')
What am I missing?
After importing the library, argparse. ArgumentParser() initializes the parser so that you can start to add custom arguments. To add your arguments, use parser. add_argument() .
Subparsers are invoked based on the value of the first positional argument, so your call would look like python test01.py A a1 -v 61. The "A" triggers the appropriate subparser, which would be defined to allow a positional argument and the -v option.
The add_argument() method action - The basic type of action to be taken when this argument is encountered at the command line. nargs - The number of command-line arguments that should be consumed. const - A constant value required by some action and nargs selections.
Make a separate parent parser and pass it to subparsers
import argparse
parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument('-H', '--host', default='192.168.122.1')
parent_parser.add_argument('-P', '--port', default='12345')
parser = argparse.ArgumentParser(add_help=False)
subparsers = parser.add_subparsers()
# subcommand a
parser_a = subparsers.add_parser('a', parents = [parent_parser])
parser_a.add_argument('-D', '--daemon', action='store_true')
parser_a.add_argument('-L', '--log', default='/tmp/test.log')
# subcommand b
parser_b = subparsers.add_parser('b', parents = [parent_parser])
parser_b.add_argument('-D', '--daemon', action='store_true')
# subcommand c
parser_c = subparsers.add_parser('c', parents = [parent_parser])
args = parser.parse_args()
print args
This gives desired result
$ python arg.py a
Namespace(daemon=False, host='192.168.122.1', log='/tmp/test.log', port='12345')
$ python arg.py b -H 127.0.0.1 -P 11111
Namespace(daemon=False, host='127.0.0.1', port='11111')
$ python arg.py c
Namespace(host='192.168.122.1', port='12345')
When you use parser
itself as a parents
of the subparsers, you recursively add subparsers
to each subparser. The add_subparsers
command actually defines a positional argument, one that gets choices, {'a','b','c'}
. It ends up expecting prog.py a a a ...
, each subparser expects another subparser command etc.
I've never seen anyone try this kind of definition, and it took a bit of thinking to realize what was happening.
@Alik's
approach is a correct one. Define the parent parser separately, and don't use it directly. It is just a source for those -H
and -P
Actions that you want added to each subparser. That's all you want to add to the subparsers.
Another approach is to simply define -H
and -P
in the main parser.
parser = argparse.ArgumentParser()
parser.add_argument('-H', '--host', default='192.168.122.1')
parser.add_argument('-P', '--port', default='12345')
subparsers = parser.add_subparsers()
# subcommand a
parser_a = subparsers.add_parser('a')
parser_a.add_argument('-D', '--daemon', action='store_true')
....
It will function in the same way, except that -H
and -P
will have to be specified before the subparser command.
0015:~/mypy$ python stack33645859.py -H 127.0.0.1 -P 1111 b
Namespace(daemon=False, host='127.0.0.1', port='1111')
They still appear in the namespace in the same way, it's just that order in the commandline is different. help
will also be different.
A third option is to add the common arguments programmatically, with a loop or function. A crude example is:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
splist = []
for cmd in ['a','b','c']:
p = subparsers.add_parser(cmd)
p.add_argument('-H', '--host', default='192.168.122.1')
p.add_argument('-P', '--port', default='12345')
splist.append(p)
splist[0].add_argument('-D', '--daemon', action='store_true')
Functionally it will be similar to @Alik's
approach, with a subtle difference. With the parent
, only one pair of H
and P
Action objects is created. References are added to each subparser.
With mine, each subparser gets its own H
and P
Action object. Each subparser could define different defaults
for those arguments. I remember this being an issue in one other SO question.
Coding work is similar in all cases.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With