Bob McBobson
Bob McBobson

Reputation: 904

How to make multiple choices be the default in an argparse argument, but one or more chocies be optional?

I want to add an argument to my parser in which there are multiple choices that can be selected from a certain argument. However, by default, I want all three options to be run by default unless the user indicates that he or she wants to run only one or two of the possible options.

import argparse

if __name__ == "__main__":
    parser = argparse.ArgumentParser()

    parser.add_argument(
        "-o",
        "--output_type",
        choices= ['count', 'proportion', 'percentage'],
        default= 'count', 'proportion', 'percentage')

    args= parser.parse_args()

    if args.output_type == ['count','proportion','percentage']:
        ###do stuff
    elif args.output_type == 'percentage' or args.output_type == 'count' or args.output_type == 'proportion':
        ###do stuff
    else:
        ###raise error

But it does not seem to work properly. It might be that my conditional tree is set up incorrectly, but I'm not sure. Can anybody advise me on how to organize this better?

Upvotes: 1

Views: 2406

Answers (2)

hpaulj
hpaulj

Reputation: 231395

If I run your code as posted I get a SyntaxError

  File "stack44160281.py", line 10
    default= 'count', 'proportion', 'percentage')
SyntaxError: non-keyword arg after keyword arg

default= 'count' is ok, but the next two strings don't fit the function's argument specs.

You probably intended it to be

default= ['count', 'proportion', 'percentage']

But now I get: NameError: name 'args' is not defined You forgot the args = parser.parse_args() line.

And the indentation of the last lines is wrong.

After correcting all those things (any of which merits a close vote)(and adding a diagnostic print(args) line).

1436:~/mypy$ python stack44160281.py 
Namespace(output_type=['count', 'proportion', 'percentage'])
do default

1436:~/mypy$ python stack44160281.py -o count
Namespace(output_type='count')
do stuff
1437:~/mypy$ python stack44160281.py -o percentage
Namespace(output_type='percentage')
do stuff
1438:~/mypy$ python stack44160281.py -o wrong
usage: stack44160281.py [-h] [-o {count,proportion,percentage}]
stack44160281.py: error: argument -o/--output_type: invalid choice: 'wrong' (choose from 'count', 'proportion', 'percentage')
1438:~/mypy$ python stack44160281.py -h
usage: stack44160281.py [-h] [-o {count,proportion,percentage}]

optional arguments:
  -h, --help            show this help message and exit
  -o {count,proportion,percentage}, --output_type {count,proportion,percentage}

The elif line could be condensed to:

elif args.output_type in ['percentage', 'count', 'proportion']:

The default could be replaced by almost any string, e.g. default='DEFAULT' and if args.output_type == 'DEFAULT' It could even be left as the default None, and tested with if args.output_type is None:.

You could have added another choice such as 'all' and use that as the default. That would let your user provide that value.

Note that the choices keeps us from getting to the else error line.

In fact the dispatching could be condensed to:

if args.output_type in ['percentage', 'count', 'proportion']:
    print('do stuff')
else:
    print('do default')

since the value is guaranteed to be one of the choices or the default.

Note this setup is not a multiple choice question. Your user still has to provide just one of the choices, not several. With a change in nargs or action we could change that. And we also need to change the testing.


If output_type has an nargs='+' parameter, then args.output_type will be a list. You could test for a specific list with:

if args.output_type == ['one', 'two']:

But what if the user gave you ['two', 'one'] instead.

Usually a list is tested with something like:

for value in args.output_type:
   if value in ['one', 'two']:
        <act>
   else if value in ['three']:
        <act>
   else:
        <act>

If you want to test for both values at once, you might use a set, so the order doesn't matter.

There's lots of ways of organizing the logic. It depends on what the values mean to you.

Upvotes: 2

Kadir
Kadir

Reputation: 1752

When i try to run your code (from python 2.7 which i am using) it gives SyntaxError: non-keyword arg after keyword arg. So if you want to set all of the choices as default you could use default built-in function, just set default = all. Secondly args should be parsed before used which is args = parser.parse_args()

Edit: As @hpaulj said, elif args.output_type == 'percentage' or args.output_type == 'count' or args.output_type == 'proportion': could be simplified as elif args.output_type in ['percentage', 'count', 'proportion']:

I hope following can help:

import argparse

if __name__ == "__main__":
    parser = argparse.ArgumentParser()

    parser.add_argument(
        "-o",
        "--output_type",
        choices = ['count', 'proportion', 'percentage'],
        default = all)

args = parser.parse_args()

if args.output_type == all:
    print "all"
elif args.output_type in ['percentage', 'count', 'proportion']:
    print "one"
else:
    print "error"

Upvotes: 0

Related Questions