18

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.

4 Answers 4

14

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')
Sign up to request clarification or add additional context in comments.

4 Comments

But you can only specify one subparser at once, do this won't work for multiple.
Can you give an example for multiple? My example comes down to optional positional arguments, both being nargs='?', so depending on what you need to do, it could be accomplished without subparsers. or multiple subparsers.. :)
I have the example in my OP: mycommand --print hello red 12 --output filename overwrite
Apologies, I should probably have read the question more clearly.. You can simulate what you're after with the nargs='+' or nargs='*', which is already as close as you came. metavar can indeed take a tuple, but can only define two names (not three). Also you could overwrite help, for something like parser.add_argument('--print', nargs='+', metavar=('text', 'color'), help='--print text [color [size]]')
8

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.

Comments

1

that will work for single arg:

parser.add_argument('--write_google', nargs='?', const='Yes',
                    choices=['force', 'Yes'],
                    help="write local changes to google")

Comments

1

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)

1 Comment

Don't forget-- he also doesn't want to have more than three arguments. If you incorporate this into your post, then you can post an independent answer (if you, of course, credit Jean Pierre for his idea.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.