I want to use argparse's {choices} parameter, but allow the user to input any number of items from choices. For example if choices is [1,2,3], I would like the following to be valid:
--arg 1
--arg 1,2
--arg 1,3
etc.
However it seems like choices doesn't accept a comma-separated input when using nargs="+". Is there any way around this? I still want to enforce that the passed in options are within the set of choices that I defined, to error-check for weird inputs.
Number of Arguments If you want your parameters to accept a list of items you can specify nargs=n for how many arguments to accept. Note, if you set nargs=1 , it will return as a list not a single value.
ArgumentParser() initializes the parser so that you can start to add custom arguments. To add your arguments, use parser. add_argument() . Some important parameters to note for this method are name , type , and required .
Metavar: It provides a different name for optional argument in help messages.
The parse_args() method actually returns some data from the options specified, in this case, echo . The variable is some form of 'magic' that argparse performs for free (i.e. no need to specify which variable that value is stored in).
I agree - You would think that the API would allow someone to do that by now right?
Well anyways, the workaround I've always used was the following:
p = argparse.ArgumentParser(description="Why doesn't argparse support list of args?")
parser.add_argument('--arg', type=str)
arg_list = parser.parse_args().args.split(",")
# if you wanted integers:
arg_list = [int(x) for x in arg_list]
AKA: Take in a string and process it yourself.
import argparse, sys
print(sys.argv)
parser = argparse.ArgumentParser()
parser.add_argument('--arg', nargs='+', choices=[1,2,3], type=int)
args = parser.parse_args()
print(args)
some runs
1455:~/mypy$ python stack49824248.py --arg 1
['stack49824248.py', '--arg', '1']
Namespace(arg=[1])
1455:~/mypy$ python stack49824248.py --arg 1 3 2 1
['stack49824248.py', '--arg', '1', '3', '2', '1']
Namespace(arg=[1, 3, 2, 1])
1456:~/mypy$ python stack49824248.py --arg 1,2
['stack49824248.py', '--arg', '1,2']
usage: stack49824248.py [-h] [--arg {1,2,3} [{1,2,3} ...]]
stack49824248.py: error: argument --arg: invalid int value: '1,2'
The shell, together with the interpreter, splits the input on spaces, and provides a list of strings in sys.argv
. That's what parser
handles.
With +
, the --arg
action accepts a list of strings (to the end or next flag). Each string is passed through the type
function, and the result compared to the choices
(if provided). In this case, type
is int
, so the choices can be integers as well. Without the type
, choices
would have to be ['1','2','3']
.
If I change the argument to:
parser.add_argument('--arg', nargs='+', choices=['1','2','3','1,2','2,3'])
it will accept some strings with commas:
1456:~/mypy$ python stack49824248.py --arg 1
['stack49824248.py', '--arg', '1']
Namespace(arg=['1'])
1505:~/mypy$ python stack49824248.py --arg 1,2
['stack49824248.py', '--arg', '1,2']
Namespace(arg=['1,2'])
1505:~/mypy$ python stack49824248.py --arg 1,2,3
['stack49824248.py', '--arg', '1,2,3']
usage: stack49824248.py [-h] [--arg {1,2,3,1,2,2,3} [{1,2,3,1,2,2,3} ...]]
stack49824248.py: error: argument --arg: invalid choice: '1,2,3' (choose from '1', '2', '3', '1,2', '2,3')
I didn't include the '1,2,3' choice, so it rejected that. Note also that I dropped the int
type
, since int('1,2')
will fail.
So if you need to accept '1,2,3', do your own split and choices test after parsing (or possibly as a custom Action
class).
In [16]: [(int(x) in [1,2,3]) for x in '1,2,3'.split(',')]
Out[16]: [True, True, True]
In [17]: [(int(x) in [1,2,3]) for x in '1,2,4'.split(',')]
Out[17]: [True, True, False]
In [18]: [(int(x) in [1,2,3]) for x in '1,a,4'.split(',')]
....
ValueError: invalid literal for int() with base 10: 'a'
This one-line code gives you all possible subsets of your choices.
from itertools import combinations, chain
allsubsets = lambda n: list(chain(*[combinations(range(n), ni) for ni in range(n+1)]))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With