Pass the nargs and const arguments to add_argument:

parser.add_argument('--list',
                    default='all',
                    const='all',
                    nargs='?',
                    choices=['servers', 'storage', 'all'],
                    help='list servers, storage, or both (default: %(default)s)')

If you want to know if --list was passed without an argument, remove the const argument, and check if args.list is None.


Documention:

nargs with '?'

One argument will be consumed from the command line if possible, and produced as a single item. If no command-line argument is present, the value from default will be produced. Note that for optional arguments, there is an additional case - the option string is present but not followed by a command-line argument. In this case the value from const will be produced.

const

When add_argument() is called with option strings (like -f or --foo) and nargs='?'. This creates an optional argument that can be followed by zero or one command-line arguments. When parsing the command line, if the option string is encountered with no command-line argument following it, the value of const will be assumed instead. See the nargs description for examples.

Answer from Francisco on Stack Overflow
🌐
Python
docs.python.org › 3 › library › argparse.html
argparse — Parser for command-line options, arguments and subcommands
Formatted choices override the default metavar which is normally derived from dest. This is usually what you want because the user never sees the dest parameter. If this display isn’t desirable (perhaps because there are many choices), just specify an explicit metavar. In general, the argparse module assumes that flags like -f and --bar indicate optional arguments, which can always be omitted at the command line.
Top answer
1 of 2
133

Pass the nargs and const arguments to add_argument:

parser.add_argument('--list',
                    default='all',
                    const='all',
                    nargs='?',
                    choices=['servers', 'storage', 'all'],
                    help='list servers, storage, or both (default: %(default)s)')

If you want to know if --list was passed without an argument, remove the const argument, and check if args.list is None.


Documention:

nargs with '?'

One argument will be consumed from the command line if possible, and produced as a single item. If no command-line argument is present, the value from default will be produced. Note that for optional arguments, there is an additional case - the option string is present but not followed by a command-line argument. In this case the value from const will be produced.

const

When add_argument() is called with option strings (like -f or --foo) and nargs='?'. This creates an optional argument that can be followed by zero or one command-line arguments. When parsing the command line, if the option string is encountered with no command-line argument following it, the value of const will be assumed instead. See the nargs description for examples.

2 of 2
16

Thanks @ShadowRanger. Subcommands is exactly what I need, combined with nargs and const. The following works:

parser = argparse.ArgumentParser()
subparser = parser.add_subparsers()
parser_list = subparser.add_parser('list')
parser_list.add_argument('list_type', default='all', const='all', nargs='?', choices=['all', 'servers', 'storage'])

parser_create = subparser.add_parser('create')
parser_create.add_argument('create_type', default='server', const='server', nargs='?', choices=['server', 'storage'])

args = parser.parse_args()
pprint(vars(args))

$ python3 ./myapp.py -h
usage: dotool.py [-h] {list,create} ...

Digital Ocean tool

positional arguments:
  {list,create}

optional arguments:
  -h, --help     show this help message and exit

list option alone:

$ python3 ./myapp.py list
{'list_type': 'all'}

List option with a parameter:

$ python3 ./myapp.py list servers
{'list_type': 'servers'}
Discussions

python - Is it better practice to set default values for optional argparse arguments? - Software Engineering Stack Exchange
In some sense, I think I'm really trying to get at the philosophy of argparse default values. For what use cases was the default parameter intended? By the way I'm aware that none of this code will run in python, I was more borrowing from python's language artifacts but in general this is all ... More on softwareengineering.stackexchange.com
🌐 softwareengineering.stackexchange.com
argparse: Problem with defaults for variable nargs when using choices
BPO 9625 Nosy @rhettinger, @macfreek, @ericvsmith, @merwok, @cedk, @berkerpeksag, @lanzz, @jameshcorbett, @zumoshi Files issue9625.diffissue9625.patchissue9625_1.patchissue9625_2.patchnotes.txt Not... More on github.com
🌐 github.com
19
August 17, 2010
argparse choices allow multiple values
specify nargs. '+' will collect 1+ arguments. https://repl.it/repls/AjarTepidMemwatch More on reddit.com
🌐 r/learnpython
1
1
February 25, 2020
equivalent of python argparse choices?
Is there an equivalent to python argparse choice, e.g.: parser.add_argument ('--rate', choices=('1/3', '1/2', '2/3', '3/4', '3/5', '4/5', '5/6', '7/9', '8/9', '9/10', '25/36', '26/45'), default='2/3') More on github.com
🌐 github.com
5
February 2, 2017
🌐
Python
bugs.python.org › issue9625
Issue 9625: argparse: Problem with defaults for variable nargs when using choices - Python tracker
August 17, 2010 - This issue tracker has been migrated to GitHub, and is currently read-only. For more information, see the GitHub FAQs in the Python's Developer Guide · This issue has been migrated to GitHub: https://github.com/python/cpython/issues/53834
🌐
Python Module of the Week
pymotw.com › 2 › argparse
argparse – Command line option and argument parsing. - Python Module of the Week
import argparse class CustomAction(argparse.Action): def __init__(self, option_strings, dest, nargs=None, const=None, default=None, type=None, choices=None, required=False, help=None, metavar=None): argparse.Action.__init__(self, option_strings=option_strings, dest=dest, nargs=nargs, const=const, ...
🌐
GitHub
github.com › python › cpython › issues › 53834
argparse: Problem with defaults for variable nargs when using choices · Issue #53834 · python/cpython
August 17, 2010 - assignee = None closed_at = None created_at = <Date 2010-08-17.09:19:03.116> labels = ['type-bug', 'library', '3.9'] title = 'argparse: Problem with defaults for variable nargs when using choices' updated_at = <Date 2020-12-21.23:36:46.142> user = 'https://bugs.python.org/thesociable' bugs.python.org fields: activity = <Date 2020-12-21.23:36:46.142> actor = 'rhettinger' assignee = 'none' closed = False closed_date = None closer = None components = ['Library (Lib)'] creation = <Date 2010-08-17.09:19:03.116> creator = 'thesociable' dependencies = [] files = ['26471', '27858', '30709', '30762', '
Author   thesociable
Find elsewhere
🌐
Plain English
python.plainenglish.io › python-case-in-sensitive-argparse-choices-a380e9169fff
Python: Case In-sensitive argparse “choices” | by Konstantinos Patronas | Python in Plain English
August 23, 2020 - More often i needed to limit the ... = "SSH Gateway.", required = False, choices = ["grvgw1","grvgw2","grvgw3","grvgw4"], default = False)...
🌐
Reddit
reddit.com › r/learnpython › argparse choices allow multiple values
r/learnpython on Reddit: argparse choices allow multiple values
February 25, 2020 -

Hello

I would like to use the choices options to limit the valid values that are passed as argument to a script

parser = argparse.ArgumentParser()
parser.add_argument('-d', type=int, default=14, help = "enter (%(type)s) number of days to query the cms (default: %(default)s) ")
parser.add_argument('-o', default="Linux", choices=['Linux', 'Windows'], help="pass OS list separated by comma (default: %(default)s)")
parser.add_argument('-s', default="02", choices=['01','02', '03', '04'], help="pass super_status (default: %(default)s)")
args = parser.parse_args()

this works fine but I am only allowed to pass one value

eg

script.py -o Windows, Linux -s 01,02

this will fail because it only accept one of the allowed values and not several.

is there a way to use the choices but allow several values i would prefer to do this instead of having to make if/then/else in the script to discard possible invalid arguments.

🌐
CopyProgramming
copyprogramming.com › howto › python-argparse-choices-with-a-default-choice
Python: Default Choice for Python argparse choices
May 6, 2023 - This is a python 3.6 approach where a lambda is used as the default. It aligns with the OP's desired outcome. The default is not immediately evaluated. You can easily locate them and invoke them in a for loop to obtain the values. To demonstrate that a regular default still functions properly ...
🌐
ZetCode
zetcode.com › python › argparse
Python argparse - parsing command line arguments in Python with argparse module
September 24, 2024 - The exponent value is not required; if not given, the default will be 2. ... The metavar option gives a name to the expected value in error and help outputs. ... #!/usr/bin/python import argparse # metavar gives name to the expected value # in error and help outputs parser = argparse.Argum...
🌐
GitHub
github.com › carlobaldassi › ArgParse.jl › issues › 39
equivalent of python argparse choices? · Issue #39 · carlobaldassi/ArgParse.jl
February 2, 2017 - Is there an equivalent to python argparse choice, e.g.: parser.add_argument ('--rate', choices=('1/3', '1/2', '2/3', '3/4', '3/5', '4/5', '5/6', '7/9', '8/9', '9/10', '25/36', '26/45'), default='2/3')
Author   nbecker
Top answer
1 of 5
17

What you need can be done using a customized argparse.Action as in the following example:

import argparse

parser = argparse.ArgumentParser()

class DefaultListAction(argparse.Action):
    CHOICES = ['clear','copy','dump','lock']
    def __call__(self, parser, namespace, values, option_string=None):
        if values:
            for value in values:
                if value not in self.CHOICES:
                    message = ("invalid choice: {0!r} (choose from {1})"
                               .format(value,
                                       ', '.join([repr(action)
                                                  for action in self.CHOICES])))

                    raise argparse.ArgumentError(self, message)
            setattr(namespace, self.dest, values)

parser.add_argument('actions', nargs='*', action=DefaultListAction,
                    default = ['dump', 'clear'],
                    metavar='ACTION')

print parser.parse_args([])
print parser.parse_args(['lock'])

The output of the script is:

$ python test.py 
Namespace(actions=['dump', 'clear'])
Namespace(actions=['lock'])
2 of 5
6

In the documentation (http://docs.python.org/dev/library/argparse.html#default), it is said :

For positional arguments with nargs equal to ? or *, the default value is used when no command-line argument was present.

Then, if we do :

acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', choices=acts, default='clear')    
print p.parse_args([])

We get what we expect

Namespace(action='clear')

The problem is when you put a list as a default. But I've seen it in the doc,

parser.add_argument('bar', nargs='*', default=[1, 2, 3], help='BAR!')

So, I don't know :-(

Anyhow, here is a workaround that does the job you want :

import sys, argparse
acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', choices=acts)
args = ['dump', 'clear'] # I set the default here ... 
if sys.argv[1:]:
    args = p.parse_args()
print args
Top answer
1 of 2
2

The behavior noted as incorrect is caused by the fact that the raw default value ['a', 'd'] is not inside the specified choices (see: relevant code as found in Python 3.4.10; this check method is effectively unchanged as of Python 3.10.3). I will reproduce the code from the Python argparse.py source code:

    def _check_value(self, action, value):
        # converted value must be one of the choices (if specified)
        if action.choices is not None and value not in action.choices:
            args = {'value': value,
                    'choices': ', '.join(map(repr, action.choices))}
            msg = _('invalid choice: %(value)r (choose from %(choices)s)')
            raise ArgumentError(action, msg % args)

When a default value is specified as a list, that entire value is passed to that _check_value method and thus it will fail (as any given list will not match any strings inside another list). You can actually verify that by setting a breakpoint with pdb in that method and trace through the values by stepping through each line, or alternatively test and verify the stated limitations with the following code:

import argparse
DEFAULT = ['a', 'd']
parser = argparse.ArgumentParser()
parser.add_argument('tests', nargs='*', choices=['a', 'b', 'c', 'd', DEFAULT],
                    default=DEFAULT)
args = parser.parse_args()
print(args.tests)

Then run python test.py

$ python test.py
['a', 'd']

This clearly passed because that very same DEFAULT value is present in the list of choices.

However, calling -h or passing any unsupported value will result in:

$ python test.py z
usage: test.py [-h] [{a,b,c,d,['a', 'd']} ...]
test.py: error: argument tests: invalid choice: 'z' (choose from 'a', 'b', 'c', 'd', ['a', 'd'])
$ python test.py -h
usage: test.py [-h] [{a,b,c,d,['a', 'd']} ...]

positional arguments:
  {a,b,c,d,['a', 'd']}
...

Which may or may not be ideal depending on use case as the output looks weird if not confusing. If this output is going to be user-facing it's probably not ideal, but if this is to maintain some internal system call emulation that won't leak out to users, the messages are probably not visible so this may be an acceptable workaround. Hence, I do not recommend this approach if the clarity of the choice message being generated is vital (which is >99% of typical use cases).

However, given that custom action is considered not ideal, I will assume overriding the ArgumentParser class may be a possible choice, and given that _check_value has not changed between 3.4 and 3.10, this might represent the most minimum additional code to nip out the incompatible check (with the specified use case as per the question):

class ArgumentParser(argparse.ArgumentParser):
    def _check_value(self, action, value):
        if value is action.default:
            return
        return super()._check_value(action, value)

This would ensure that the default value be considered a valid choice (return None if the value is the action's default, otherwise return the default check) before using the default implementation that is unsuitable for the requirement as outlined in the question; do please note that this prevents deeper inspection of what that action.default provides being a valid one (if that's necessary, custom Action class is most certainly the way to go).

Might as well show the example usage with the custom class (i.e. copy/pasted the original code, remove the argparse. to use the new custom class):

parser = ArgumentParser()
parser.add_argument('tests', nargs='*', choices=['a', 'b', 'c', 'd'],
                    default=['a', 'd'])
args = parser.parse_args()
print(args.tests)

Usage:

$ python test.py
['a', 'd']
$ python test.py a z
usage: test.py [-h] [{a,b,c,d} ...]
test.py: error: argument tests: invalid choice: 'z' (choose from 'a', 'b', 'c', 'd')
$ python test.py -h
usage: test.py [-h] [{a,b,c,d} ...]

positional arguments:
  {a,b,c,d}

optional arguments:
  -h, --help  show this help message and exit
2 of 2
0

I'm not sure why you need a custom action. Maybe it's not applicable to your conditions, but a simple way to handle this is to move the default handling to post-process instead of let argparse do it. So instead of passing the default argument, you can do something like this:

parser = argparse.ArgumentParser()
parser.add_argument('tests', nargs='*', choices=['a', 'b', 'c', 'd'])
args = parser.parse_args()

tests = args.tests or ['a', 'd']
print(tests)

You get all the benefits of argparse's internal choices validation, but with proper default acceptence.

Your example runs give these results with the above code:

$ ./test.py a
['a']
$ ./test.py a d
['a', 'd']
$ ./test.py a e
usage: test.py [-h] [{a,b,c,d} ...]
test.py: error: argument tests: invalid choice: 'e' (choose from a, b, c, d)
$ ./test.py
['a', 'd']
🌐
Flexiple
flexiple.com › python › python-argparse-list
Python argparse - Flexiple
March 27, 2024 - # Add an optional argument with a default value parser.add_argument('--verbose', action='store_true', help='Print verbose output') ... Argparse gives you the power to validate user input and restrict it to certain choices. For instance, you can specify that an argument must be an integer or a specific string.
🌐
Janert
janert.me › blog › 2022 › command-line-arguments-with-pythons-argparse
Command Line Arguments with Python's Argparse Module - Philipp K. Janert, Ph.D.
November 11, 2022 - The default is "store", meaning that the value is stored in the object returned by parse_args(). Other possibilities include "count", which returns the number of times the option has been given on the command line, and a few others (see below). In addition to parsing the command-line arguments, ...
🌐
Tom Ron
tomron.net › 2020 › 02 › 23 › argparse-choices
argparse choices – Tom Ron
February 23, 2020 - I saw this post in Medium regarding argparse, which suggests the following - parser.add_argument("--model", help="choose model architecture from: vgg19 vgg16 alexnet", type=str) I think the following variant is better - parser.add_argument("--model", help="choose model architecture from: vgg19 vgg16 alexnet", type=str, choices=['vgg19', 'vgg16', 'alexnet'], default='alexnet']) If an illegal parameter is given, for example --model vgg20, the desired…
Top answer
1 of 2
2

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.

2 of 2
0

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"
🌐
mkaz.blog
mkaz.blog › working-with-python › argparse
Parse Command-Line Arguments with Argparse
September 19, 2025 - parser = argparse.ArgumentParser() parser.add_argument('-c', action='append') args = parser.parse_args() print("~ C: {}".format(args.c)) ... $ python test.py ~ C: None $ python test.py -c hi ~ C: ['hi'] $ python test.py -c hi -c hello -c hey ~ C: ['hi', 'hello', 'hey'] If you only want a set of allowed values to be used, you can set the choices list, which will display an error if invalid entry.
🌐
OMZ Software
omz-software.com › editorial › docs › library › argparse.html
15.4. argparse — Parser for command-line options, arguments and sub-commands — Editorial Documentation
These can be handled by passing a container object as the choices keyword argument to add_argument(). When the command line is parsed, argument values will be checked, and an error message will be displayed if the argument was not one of the acceptable values: >>> parser = argparse.ArgumentParser(prog='game.py') >>> parser.add_argument('move', choices=['rock', 'paper', 'scissors']) >>> parser.parse_args(['rock']) Namespace(move='rock') >>> parser.parse_args(['fire']) usage: game.py [-h] {rock,paper,scissors} game.py: error: argument move: invalid choice: 'fire' (choose from 'rock', 'paper', 'scissors')