To elaborate on my comments.
Normal processing of defaults, as @Tomerikoo quotes, is to process string defaults through both the type
and choices
check. The defaults are placed in the args
namespace at the start of parsing. At the end, if the defaults have not be overwritten by using inputs, they will conditionally converted. Non-string defaults are left as is, without type
or choices
checking.
But a positional with nargs='*'
is different. It is "always seen", since an empty list (i.e. none) of strings satisfies its nargs
. With usual handling this would overwrite the default
, resulting in Namespace(choices=[])
.
To get around that the _get_values
method replaces the []
with the default
. This new value is passed through the choices
test, but not through type
. This results in the behavior that the OP encountered.
The relevant parts of _get_values
are quoted below. The first handles nargs='?'
, the second '*', and the third normal cases. _get_value
does the type
test. _check_value
does the choices
test.
def _get_values(self, action, arg_strings):
....
# optional argument produces a default when not present
if not arg_strings and action.nargs == OPTIONAL:
if action.option_strings:
value = action.const
else:
value = action.default
if isinstance(value, str):
value = self._get_value(action, value)
self._check_value(action, value)
# 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)
# single argument or optional argument produces a single value
elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:
arg_string, = arg_strings
value = self._get_value(action, arg_string)
self._check_value(action, value)
I should add another block, which is used for '*' and non-empty user input:
# all other types of nargs produce a list
else:
value = [self._get_value(action, v) for v in arg_strings]
for v in value:
self._check_value(action, v)
Note that here, individual strings are type
and checked
, where as in the first case, it's the whole default
that is checked
.
There have been bug/issues related to this, but no one has some up with a good reason to change it.