I'd like to have an argument to my program that has some required parameters along with some optional parameters. Something like this:
[--print text [color [size]]
so you could pass it any of these:
mycommand --print hello
mycommand --print hello blue
mycommand --print hello red 12
There could be multiple of these so it has to be a single add_argument. For example:
[--print text [color]] [--output filename [overwrite]]
I can achieve arguments that are close to what I want:
>>> parser = argparse.ArgumentParser()
>>> act = parser.add_argument('--foo', nargs=3, metavar=('x','y','z'))
>>> act = parser.add_argument('--bar', nargs='?')
>>> act = parser.add_argument('--baz', nargs='*')
>>> parser.print_help()
usage: [-h] [--foo x y z] [--bar [BAR]] [--baz [BAZ [BAZ ...]]]
optional arguments:
-h, --help show this help message and exit
--foo x y z
--bar [BAR]
--baz [BAZ [BAZ ...]]
but not quite. Is there any way to do this with argparse? I know I could make them all nargs="*"
but then --help would not list the names of the optional arguments. If I pass nargs="*"
and a tuple for metavar, argparse throws an exception.
Reading the source code (start in take_action
), I believe what you want is impossible. All argument parsing and passing to actions is done based on nargs, and nargs is either a number, OPTIONAL
("?"), ZERO_OR_MORE
("*"), ONE_OR_MORE
("+"), PARSER
, or REMAINDER
. This must be determined before the Action object (which handles the input) even sees what it's getting, so it can't dynamically figure out nargs
.
I think you'll need to live with a workaround. I would maybe have --foo-x x
, --foo-y y
, and --foo-z z
, and perhaps also --foo x y z
.
How about
def printText(args):
print args
parser = argparse.ArgumentParser()
subparser = parser.add_subparsers()
printer = subparser.add_parser('print')
printer.add_argument('text')
printer.add_argument('color', nargs='?')
printer.add_argument('size', type=int, nargs='?')
printer.set_defaults(func=printText)
cmd = parser.parse_args()
cmd.func(cmd)
Then you get something like this:
$ ./test.py -h
usage: test.py [-h] {print} ...
positional arguments:
{print}
$ ./test.py print -h
usage: test.py print [-h] text [color] [size]
positional arguments:
text
color
size
$ ./test.py print text
Namespace(color=None, func=<function printText at 0x2a96150b90>, size=None, text='text')
$ ./test.py print text red
Namespace(color='red', func=<function printText at 0x2a96150b90>, size=None, text='text')
$ ./test.py print text red 12
Namespace(color='red', func=<function printText at 0x2a96150b90>, size=12, text='text')
According to Devin Jeanpierre's answer, it seems that using '+' (one or more) instead of '*' would do what you are trying to achieve.
(PS: I would've just commented in his answer if I had enough points)
that will work for single arg:
parser.add_argument('--write_google', nargs='?', const='Yes',
choices=['force', 'Yes'],
help="write local changes to google")