You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
argparse does not honor default argument for nargs=argparse.REMAINDER argument #79676
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee=Noneclosed_at=Nonecreated_at=<Date2018-12-14.15:59:40.287>labels= ['type-bug', 'library', '3.9']
title='argparse does not honor default argument for nargs=argparse.REMAINDER argument'updated_at=<Date2019-07-18.21:03:41.175>user='https://bugs.python.org/rgov'
argparse.REMAINDER matches an empty list of arguments, just like '?' and '*'. So they are always 'filled', even by parse_args([]).
'?' and '*' have some special handling of defaults in this case, see in
argparse.ArgumentParser._get_values
the two
value=action.default
REMAINDER has its own section in the function that does nothing with the default.
I think it should be left as is.
mblahay commented on May 7, 2019
mblahaymannequin
Mannequin
More actions
Ryan, what are the exact steps to reproduce the problem? This is what I get when I run the code you included:
>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('things', nargs=argparse.REMAINDER, default=['nothing'])
_StoreAction(option_strings=[], dest='things', nargs='...', const=None, default=['nothing'], type=None, choices=None, help=None, metavar=None)
>>> parser.parse_args([])
Namespace(things=[])
>>> Namespace(things=[])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'Namespace' is not defined
rgov commented on May 8, 2019
rgovmannequin
MannequinAuthor
More actions
Just don’t run the last line which is just an echoing of the output of
parser.parse_args() repeated. The Namespace type would need to be imported
if you really wanted to but there’s no point.
>
> Michael Blahay <mblahay@gmail.com> added the comment:
>
> Ryan, what are the exact steps to reproduce the problem? This is what I
> get when I run the code you included:
>
> >>> import argparse
> >>> parser = argparse.ArgumentParser()
> >>> parser.add_argument('things', nargs=argparse.REMAINDER,
> default=['nothing'])
> _StoreAction(option_strings=[], dest='things', nargs='...', const=None,
> default=['nothing'], type=None, choices=None, help=None, metavar=None)
> >>> parser.parse_args([])
> Namespace(things=[])
> >>> Namespace(things=[])
> Traceback (most recent call last):
> File "<stdin>", line 1, in <module>
> NameError: name 'Namespace' is not defined
>
>
Okay, so the expected output after running parse.parse_args([]) is Namespace(['nothing'])
mblahay commented on May 8, 2019
mblahaymannequin
Mannequin
More actions
Ryan, I have reviewed the documentation at https://docs.python.org/3/library/argparse.html#nargs and must admit that there is not a definitive answer that I can see regarding the defined behavior should there be no command line arguments that are in fact remaining. One could certainly argue that the empty list is the expression of the fact that no remaining arguments could be found. One can also argue that when seeking the remaining arguments, a list that may be zero to many elements in size, that by definition there cannot be a default.
Can you cite any documentation that would support your claim?
mblahay commented on May 10, 2019
mblahaymannequin
Mannequin
More actions
For the purpose of facilitating continuing conversation, here are two tests that contrast the use of * versus REMAINDER
It is important to note that when an optional argument is not present then the default is always used, including for one using nargs=argparse.REMAINDER. In all three tests, bar is the argument using REMAIDER. In the first test, one can see that when bar isn't the last argument then anything else, including other arguments, are swept up as being arguments of bar. This greedy behavior for REMAINDER is something that * does not share (test 2).
mblahay commented on May 10, 2019
mblahaymannequin
Mannequin
More actions
With the optional arguments, the determination about whether to use the default value is made based on whether the flag is present or not. When positional arguments are involved, the need for the defaults seems to in part be determined based on whether the argument exists. The fact that * and REMAINDER are zero-to-many in nature add some ambiguity into the situation. For the *, it seems that the positional argument only exists if there is at least one actual argument value that it can consume.
To conclude, * and REMAINDER perform similar, but different, roles when used with positional arguments. With edge cases like the ones laid out above, it can be hard to conceptualize what the exact behavior should be. I will recommend that the documentation be updated to convey the following message: "When used with positional arguments, REMAINDER will never use the designated default value list. It will instead return an empty list if there are no values for the argument to consume. If the use of default values is desired, then * must be used."
mblahay commented on May 10, 2019
mblahaymannequin
Mannequin
More actions
Much detail has been provided regarding why the default is ignored when user the REMAINDER option. The desire to add an exception has not. Is there anyone that can provide guidance on whether the combination of:
Positional Argument
nargs=REMAINDER
default=something
should raise an exception upon execution of the add_argument method?
paulj3 commented on May 11, 2019
paulj3mannequin
Mannequin
More actions
At the start of parse_known_args, all defaults (except SUPPRESS ones) are placed in the namespace:
# add any action defaults that aren't presentforactioninself._actions:
ifaction.destisnotSUPPRESS:
ifnothasattr(namespace, action.dest):
ifaction.defaultisnotSUPPRESS:
setattr(namespace, action.dest, action.default)
at the end of _parse_known_args there's a conditional expression that cleans up remaining defaults that are strings, by passing them through the 'type` callable:
Read the comments to see why this default setting is done in two parts.
The pattern of defaults in msg342122 with optionals is consistent with that. If the argument is not provided, the default appears.
In the first example of that message, the REMAINDER is given all the remaining strings including the '--bar', so there is nothing left to trigger the '--bar' optional argument, and it retains the default.
The difference for positionals is due to how the '*' and '...' are handled in _getvalues. Both may be filled with an empty list of values.
# 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)
....
# REMAINDER arguments convert all values, checking none
elif action.nargs == REMAINDER:
value = [self._get_value(action, v) for v in arg_strings]
In the case of '*', the default is, effectively, placed back on the namespace. REMAINDER does not - the empty list is put in the namespace.
In _get_positional_kwargs,
# mark positional arguments as required if at least one is# always requiredifkwargs.get('nargs') notin [OPTIONAL, ZERO_OR_MORE]:
kwargs['required'] =Trueifkwargs.get('nargs') ==ZERO_OR_MOREand'default'notinkwargs:
kwargs['required'] =True
That last conditional is a little puzzling, but I suspect it has to do with mutually_exclusive_groups, A '*' positional can be a member of a group if it has a default.
Anyways, we could add a test at this point like:
ifkwargs.get('nargs') ==REMAINDERand'default'inkwargs:
msg=_("'default' is not allowed with a REMAINDER positional")
raiseTypeError(msg)
But reviewing the code I notice another difference. 'choices' are not honored for REMAINDER. That makes a lot of sense. REMAINDER is supposed to be a catch all, documented as something that might be passed on to another parser. This parser shouldn't be doing anything with those values.
The documentation reads:
argparse.REMAINDER. All the remaining command-line arguments are gathered into a list. This is commonly useful for command line utilities that dispatch to other command line utilities:
I think REMAINDER has another quirk. It doesn't work as the first (and only?) argument. There should be a bug/issue to that effect.
In https://bugs.python.org/issue17050#msg315716, I suggest removing REMAINDER from the docs. We can leave the code as is, in case anyone is using still using it. But it is probably too much work to make the code and docs match, both for that issue, and for this.
'*' plus '--' gives almost the same behavior. So does parse_known_args.
2 remaining items
mblahay commented on May 27, 2019
mblahaymannequin
Mannequin
More actions
Ryan, last chance, do you have any feedback?
rgov commented on May 27, 2019
rgovmannequin
MannequinAuthor
More actions
Thanks Michael for all of the examples. After reading them all, I concur that "it can be hard to conceptualize what the exact behavior should be." A documentation change is warranted, at the least.
However the argparse documentation, while great, is dense and it would be easy to overlook a simple comment. And I think the point that is being raised isn't merely a suggestion on how to design a good CLI, but a pitfall that makes the behavior of code non-obvious---it's not something someone would necessarily consult the documentation for while reviewing code.
(By the way, I'm considering CLIs like docker run and ssh which take an optional command to execute, and when absent, fall back on default behavior.)
So I would prefer a code change that makes it harder to write code that hits this corner case. Potential solutions would be either
(a) making a positional REMAINDER arg with a default value an error, as in Paul's proposed change; or
(b) making a default value with a positional REMAINDER arg 'just work'
I think (a) is the most reasonable. The exception can recommend the use of nargs='*' instead, which makes it actionable. And it is unlikely that the exception would be buried down some untested code path that would end up in released code.
(Perhaps it's also worth raising an exception when adding a positional argument after a nargs>1 positional argument already exists, but that's another issue.)
mblahay commented on Jun 17, 2019
mblahaymannequin
Mannequin
More actions
Need some help searching github to determine the blast radius of the proposed changes. How does one look for instances of argparse.REMAINDER that are used with a default value?
mblahay commented on Jul 18, 2019
mblahaymannequin
Mannequin
More actions
Ryan, I like option A as well, but it is a breaking change. Unlike in a compiled language where we could output a warning, making the proposed change could bring some software to a grinding halt. For now I'm going to make the documentation change and this issue can stay open for if there is a major version change that would allow such a breaking change.
To summarize the problem: when a positional uses nargs=REMAINDER, its default is ignored -- parse_args() returns [] instead of the default.
Going through the previous discussion, REMAINDER is an undocumented legacy feature (source). It still exists in the source, but all mentions were removed from the docs in GH-18661 and GH-103586
As for the fix, there are two possible options, copying the answer from the comment above:
(a) making a positional REMAINDER arg with a default value an error, as in Paul's proposed change; or
(b) making a default value with a positional REMAINDER arg 'just work'
Since the issue is quite old and REMAINDER is a legacy feature, I'd assume we can close this issue? If you'd prefer to ship a fix instead, let me know which and I can look into implementing a) or b).
While the default is indeed ignored here, REMAINDER is still widely used (thousands of usages from a cursory search on GitHub), and both possible fixes carry real backwards compatibility risk that I'm not sure is worth it. Making it an error would break programs that currently pass a default harmlessly, and honoring the default would silently change results for anyone relying on the current [].
Since far more code could break than would be helped, I'd rather leave the behavior stable. It's a real rough edge; if there's broader demand or it comes up in larger parsing work, we can revisit.
Activity
rgov commentedon Dec 14, 2018
Since there were no unparsed arguments remaining, the
defaultsetting forthingsshould have been honored. However it silently ignores this setting.If there's a reason why this wouldn't be desirable, it should raise an exception that the options aren't compatible.
paulj3 commentedon Dec 26, 2018
argparse.REMAINDER matches an empty list of arguments, just like '?' and '*'. So they are always 'filled', even by
parse_args([]).'?' and '*' have some special handling of defaults in this case, see in
the two
REMAINDER has its own section in the function that does nothing with the default.
I think it should be left as is.
mblahay commentedon May 7, 2019
Ryan, what are the exact steps to reproduce the problem? This is what I get when I run the code you included:
rgov commentedon May 8, 2019
Just don’t run the last line which is just an echoing of the output of
parser.parse_args() repeated. The Namespace type would need to be imported
if you really wanted to but there’s no point.
On Tuesday, May 7, 2019, Michael Blahay <report@bugs.python.org> wrote:
> > Michael Blahay <mblahay@gmail.com> added the comment: > > Ryan, what are the exact steps to reproduce the problem? This is what I > get when I run the code you included: > > >>> import argparse > >>> parser = argparse.ArgumentParser() > >>> parser.add_argument('things', nargs=argparse.REMAINDER, > default=['nothing']) > _StoreAction(option_strings=[], dest='things', nargs='...', const=None, > default=['nothing'], type=None, choices=None, help=None, metavar=None) > >>> parser.parse_args([]) > Namespace(things=[]) > >>> Namespace(things=[]) > Traceback (most recent call last): > File "<stdin>", line 1, in <module> > NameError: name 'Namespace' is not defined > >mblahay commentedon May 8, 2019
Okay, so the expected output after running parse.parse_args([]) is Namespace(['nothing'])
mblahay commentedon May 8, 2019
Ryan, I have reviewed the documentation at https://docs.python.org/3/library/argparse.html#nargs and must admit that there is not a definitive answer that I can see regarding the defined behavior should there be no command line arguments that are in fact remaining. One could certainly argue that the empty list is the expression of the fact that no remaining arguments could be found. One can also argue that when seeking the remaining arguments, a list that may be zero to many elements in size, that by definition there cannot be a default.
Can you cite any documentation that would support your claim?
mblahay commentedon May 10, 2019
For the purpose of facilitating continuing conversation, here are two tests that contrast the use of * versus REMAINDER
Out[7]: Namespace(bar=['b', 'c'], baz=['nada'], foo=['a'])
Out[8]: Namespace(bar=[], baz=['b', 'c'], foo=['a'])
You can see that * and REMAINDER do differ in functionality when they are the last defined argument.
mblahay commentedon May 10, 2019
Here is another take on the issue, this time illustrated through the lens of optional arguments.
Out[9]: Namespace(bar=['b', '--baz', 'c'], baz=['nada'], foo=['a'])
Out[10]: Namespace(bar=['c'], baz=['b'], foo=['a'])
Out[11]: Namespace(bar=['nothing'], baz=['nada'], foo=['none'])
It is important to note that when an optional argument is not present then the default is always used, including for one using nargs=argparse.REMAINDER. In all three tests, bar is the argument using REMAIDER. In the first test, one can see that when bar isn't the last argument then anything else, including other arguments, are swept up as being arguments of bar. This greedy behavior for REMAINDER is something that * does not share (test 2).
mblahay commentedon May 10, 2019
With the optional arguments, the determination about whether to use the default value is made based on whether the flag is present or not. When positional arguments are involved, the need for the defaults seems to in part be determined based on whether the argument exists. The fact that * and REMAINDER are zero-to-many in nature add some ambiguity into the situation. For the *, it seems that the positional argument only exists if there is at least one actual argument value that it can consume.
Out[25]: Namespace(baz=['b'], foo=['a'])
Out[26]: Namespace(baz=['nada'], foo=['a'])
Mean while, the REMAINDER option makes the argument act as if it exists regardless of whether an actual argument value exists.
Out[27]: Namespace(bar=['b'], foo=['a'])
Out[28]: Namespace(bar=[], foo=['a'])
To conclude, * and REMAINDER perform similar, but different, roles when used with positional arguments. With edge cases like the ones laid out above, it can be hard to conceptualize what the exact behavior should be. I will recommend that the documentation be updated to convey the following message: "When used with positional arguments, REMAINDER will never use the designated default value list. It will instead return an empty list if there are no values for the argument to consume. If the use of default values is desired, then * must be used."
mblahay commentedon May 10, 2019
Much detail has been provided regarding why the default is ignored when user the REMAINDER option. The desire to add an exception has not. Is there anyone that can provide guidance on whether the combination of:
should raise an exception upon execution of the add_argument method?
paulj3 commentedon May 11, 2019
At the start of parse_known_args, all defaults (except SUPPRESS ones) are placed in the namespace:
at the end of _parse_known_args there's a conditional expression that cleans up remaining defaults that are strings, by passing them through the 'type` callable:
Read the comments to see why this default setting is done in two parts.
The pattern of defaults in msg342122 with optionals is consistent with that. If the argument is not provided, the default appears.
In the first example of that message, the REMAINDER is given all the remaining strings including the '--bar', so there is nothing left to trigger the '--bar' optional argument, and it retains the default.
The difference for positionals is due to how the '*' and '...' are handled in _getvalues. Both may be filled with an empty list of values.
In the case of '*', the default is, effectively, placed back on the namespace. REMAINDER does not - the empty list is put in the namespace.
In _get_positional_kwargs,
That last conditional is a little puzzling, but I suspect it has to do with mutually_exclusive_groups, A '*' positional can be a member of a group if it has a default.
Anyways, we could add a test at this point like:
But reviewing the code I notice another difference. 'choices' are not honored for REMAINDER. That makes a lot of sense. REMAINDER is supposed to be a catch all, documented as something that might be passed on to another parser. This parser shouldn't be doing anything with those values.
The documentation reads:
argparse.REMAINDER. All the remaining command-line arguments are gathered into a list. This is commonly useful for command line utilities that dispatch to other command line utilities:
I think REMAINDER has another quirk. It doesn't work as the first (and only?) argument. There should be a bug/issue to that effect.
https://bugs.python.org/issue17050, argparse.REMAINDER doesn't work as first argument
In https://bugs.python.org/issue17050#msg315716, I suggest removing REMAINDER from the docs. We can leave the code as is, in case anyone is using still using it. But it is probably too much work to make the code and docs match, both for that issue, and for this.
'*' plus '--' gives almost the same behavior. So does parse_known_args.
2 remaining items
mblahay commentedon May 27, 2019
Ryan, last chance, do you have any feedback?
rgov commentedon May 27, 2019
Thanks Michael for all of the examples. After reading them all, I concur that "it can be hard to conceptualize what the exact behavior should be." A documentation change is warranted, at the least.
However the argparse documentation, while great, is dense and it would be easy to overlook a simple comment. And I think the point that is being raised isn't merely a suggestion on how to design a good CLI, but a pitfall that makes the behavior of code non-obvious---it's not something someone would necessarily consult the documentation for while reviewing code.
(By the way, I'm considering CLIs like
docker runandsshwhich take an optional command to execute, and when absent, fall back on default behavior.)So I would prefer a code change that makes it harder to write code that hits this corner case. Potential solutions would be either
(a) making a positional REMAINDER arg with a default value an error, as in Paul's proposed change; or
(b) making a default value with a positional REMAINDER arg 'just work'
I think (a) is the most reasonable. The exception can recommend the use of nargs='*' instead, which makes it actionable. And it is unlikely that the exception would be buried down some untested code path that would end up in released code.
(Perhaps it's also worth raising an exception when adding a positional argument after a nargs>1 positional argument already exists, but that's another issue.)
mblahay commentedon Jun 17, 2019
Need some help searching github to determine the blast radius of the proposed changes. How does one look for instances of argparse.REMAINDER that are used with a default value?
mblahay commentedon Jul 18, 2019
Ryan, I like option A as well, but it is a breaking change. Unlike in a compiled language where we could output a warning, making the proposed change could bring some software to a grinding halt. For now I'm going to make the documentation change and this issue can stay open for if there is a major version change that would allow such a breaking change.
clytaemnestra commentedon Jun 1, 2026
@savannahostrowski I tested this against 3.14 and the issue still exists:
To summarize the problem: when a positional uses
nargs=REMAINDER, its default is ignored --parse_args()returns[]instead of the default.Going through the previous discussion, REMAINDER is an undocumented legacy feature (source). It still exists in the source, but all mentions were removed from the docs in GH-18661 and GH-103586
As for the fix, there are two possible options, copying the answer from the comment above:
(a) making a positional REMAINDER arg with a default value an error, as in Paul's proposed change; or
(b) making a default value with a positional REMAINDER arg 'just work'
Since the issue is quite old and
REMAINDERis a legacy feature, I'd assume we can close this issue? If you'd prefer to ship a fix instead, let me know which and I can look into implementing a) or b).savannahostrowski commentedon Jun 9, 2026
Thanks for the thorough investigation, @clytaemnestra!
While the default is indeed ignored here, REMAINDER is still widely used (thousands of usages from a cursory search on GitHub), and both possible fixes carry real backwards compatibility risk that I'm not sure is worth it. Making it an error would break programs that currently pass a default harmlessly, and honoring the default would silently change results for anyone relying on the current [].
Since far more code could break than would be helped, I'd rather leave the behavior stable. It's a real rough edge; if there's broader demand or it comes up in larger parsing work, we can revisit.