I'm trying to set up an argument that accepts one or more values from a given list of choices, but is not obligatory. I'm trying this (with a couple of variants that also don't work as expected):
parser.add_argument("FLAGS", nargs='*', choices=["X","Y","Z","ALL"])
I expect to get a list of values from the list of choices, or an empty list if nothing was given (that, I think, should be enforced by nargs='*'
). But regardless of whether I add default=""
or not, when I don't pass any argument it fails with:
error: argument FLAGS: invalid choice: []
How to achieve what I need?
CodePudding user response:
This probably doesn't fit your needs, but you can do it easily with an option like --flags
.
parser.add_argument(
"--flags",
nargs='*',
default=[], # Instead of "None"
choices=["X", "Y", "Z", "ALL"])
args = parser.parse_args()
print(args)
$ tmp.py
Namespace(flags=[])
$ tmp.py --flags
Namespace(flags=[])
$ tmp.py --flags X
Namespace(flags=['X'])
$ tmp.py --flags X Z
Namespace(flags=['X', 'Z'])
$ tmp.py --flags foobar
usage: tmp.py [-h] [--flags [{X,Y,Z,ALL} ...]]
tmp.py: error: argument --flags: invalid choice: 'foobar' (choose from 'X', 'Y', 'Z', 'ALL')
$ tmp.py --help
usage: tmp.py [-h] [--flags [{X,Y,Z,ALL} ...]]
optional arguments:
-h, --help show this help message and exit
--flags [{X,Y,Z,ALL} ...]
CodePudding user response:
A positional '*' gets some special handling. Its nargs
is satisfied with an empty list (nothing). It's always handled. What does it mean to check an empty list of strings against the choices
?
So the get_values()
method does:
# when nargs='*' on a positional, if there were no command-line
# args, use the default if it is anything other than None
elif (not arg_strings and action.nargs == ZERO_OR_MORE and
not action.option_strings):
if action.default is not None:
value = action.default
else:
value = arg_strings
self._check_value(action, value)
where _check_value
tests if value
is in the choices
.
Such a positional is best used with a valid default
.
In [729]: p=argparse.ArgumentParser()
In [730]: a=p.add_argument("FLAGS", nargs='*', choices=["X","Y","Z","ALL"])
In [731]: p.parse_args([])
usage: ipython3 [-h] [{X,Y,Z,ALL} [{X,Y,Z,ALL} ...]]
ipython3: error: argument FLAGS: invalid choice: [] (choose from 'X', 'Y', 'Z', 'ALL')
...
Testing an empty list against choices
fails:
In [732]: a.choices
Out[732]: ['X', 'Y', 'Z', 'ALL']
In [733]: [] in a.choices
Out[733]: False
In [734]: 'X' in a.choices
Out[734]: True
If we set a valid default:
In [735]: a.default='X'
In [736]: p.parse_args([])
Out[736]: Namespace(FLAGS='X')
This behavior is part of what allows us to use such a positional in a mutually_exclusive_group
.
If you don't want to specify a valid default, then changing this to a flagged argument avoids the problem.