An argparse limitation with nargs="*"
and choices=...
Suppose that you are writing a command where the user can specify
one or more additional things for the command to do, as command
line arguments. The basic command might be invoked as 'dothing
',
but you can also run it as 'dothing also-a also-b
' (and variants).
There are a limited set of valid additional things to do, and trying
to do anything else is an error.
On the surface, it looks like argparse can handle this:
parser.add_argument("cmds", nargs="*", choices=("also-a", "also-b", "also-c"), help="Additional things to do")
If you run this with additional arguments, all will seem well. Your allowed additional arguments will be accepted, and any other additional arguments will be rejected with an appropriately specific error message. Then perhaps you will try to run this without any additional arguments:
usage: .... dothing: error: argument cmds: invalid choice: [] (choose from 'also-a', 'also-b', 'also-c')
I will skip to the conclusion: I've been unable to come up with a
good way to fix this. If you need choices=
, argparse effectively
turns nargs="*"
into nargs="+"
. Otherwise, you can't specify a
choices=
and must do the validation yourself.
(This issue happens in both Python 2 and Python 3 in my testing. I first ran into it in Python 2, but have seen it in Python 3 as well.)
It's possible to sort of work around this by setting a 'default=
'
value, but it seems that the default value you need here is a bit
non-functional too. Since we've set nargs="*"
, the proper end
value of parser.cmds
is a list (and this is what you get if one
or more explicit arguments is provided), so you'd expect that you
want to set default
to a list. If you do that, it is rejected as
not matching your choices
selection. If you set default
to a
single value in your choices
, eg 'default="also-c"
, it's accepted
but parser.cmds
isn't a list if the default is used; instead of
being '["also-c"]
', it is plain '"also-c"
'. You can make your
code detect this situation and even use it to tell if an argument
was provided explicitly, but the whole thing feels like a fragile
house of cards that's going to come tumbling down some day.
(If I was energetic I would try to file this as a Python bug. I may be sufficiently energetic to do this some day, but not right now.)
Comments on this page:
|
|