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
defaultwill 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 fromconstwill be produced.
const
Answer from Francisco on Stack OverflowWhen
add_argument()is called with option strings (like-for--foo) andnargs='?'. 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 ofconstwill be assumed instead. See the nargs description for examples.
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
defaultwill 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 fromconstwill be produced.
const
When
add_argument()is called with option strings (like-for--foo) andnargs='?'. 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 ofconstwill be assumed instead. See the nargs description for examples.
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'}
python - Is it better practice to set default values for optional argparse arguments? - Software Engineering Stack Exchange
argparse: Problem with defaults for variable nargs when using choices
argparse choices allow multiple values
equivalent of python argparse choices?
Videos
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--example', nargs='?', const=1, type=int)
args = parser.parse_args()
print(args)
% test.py
Namespace(example=None)
% test.py --example
Namespace(example=1)
% test.py --example 2
Namespace(example=2)
nargs='?'means 0-or-1 argumentsconst=1sets the default when there are 0 argumentstype=intconverts the argument to int
If you want test.py to set example to 1 even if no --example is specified, then include default=1. That is, with
parser.add_argument('--example', nargs='?', const=1, type=int, default=1)
then
% test.py
Namespace(example=1)
The difference between:
parser.add_argument("--debug", help="Debug", nargs='?', type=int, const=1, default=7)
and
parser.add_argument("--debug", help="Debug", nargs='?', type=int, const=1)
is thus:
myscript.py => debug is 7 (from default) in the first case and "None" in the second
myscript.py --debug => debug is 1 in each case
myscript.py --debug 2 => debug is 2 in each case
The premise behind your question is mistaken.
>>> import argparse
>>> p = argparse.ArgumentParser()
>>> p.add_argument('-a')
>>> p.parse_args()
Namespace(a=None)
The -a argument is already optional by default (you don't need required=False), and its default is already None, ensuring that args.a exists without any manual intervention.
You have to opt-in to having no default at all.
>>> p.add_argument("-b", default=argparse.SUPPRESS)
>>> p.parse_args()
Namespace(a=None)
So yes, it probably is a good idea to have some default, and argparse acknowledges that by providing one.
So some clarifications here. To answer this question for python specifically, argparse does in fact default optional argument values to None. Your problem arises from having multiple subcommands that take different arguments, but go through the same code flow. If you pass the same args from subcommands a, b, c that have different options, then you do have to handle each option that is not common across all three. In that workflow, I would recommend backfilling each option that is not guaranteed to exist.
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.
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'])
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
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
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']
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.
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"