This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Argparse complains argument required when default is provided
Type: behavior Stage:
Components: Library (Lib) Versions: Python 3.8
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Clint Olsen, eric.smith, paul.j3, rhettinger
Priority: normal Keywords:

Created on 2021-02-10 10:24 by Clint Olsen, last changed 2022-04-11 14:59 by admin.

Messages (8)
msg386770 - (view) Author: Clint Olsen (Clint Olsen) Date: 2021-02-10 10:24
When I run the following program, I expect args.run to be 'foo' if no argument is specified on the command-line.

parser = argparse.ArgumentParser()

parser.add_argument('foo', default='bar')

args = parser.parse_args()

$ ./test
usage: test [-h] foo
test: error: the following arguments are required: foo

However if I specify nargs='*' this error goes away.

Maybe I'm missing something obvious, but this seems non-intuitive to me.
msg386771 - (view) Author: Clint Olsen (Clint Olsen) Date: 2021-02-10 10:26
Sorry, I meant to say args.foo should be 'bar'.
msg386781 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2021-02-10 14:27
Providing a default value does not imply setting nargs, so you need to specify both. I think it's not clear what nargs would default to, if you wanted to change the behavior if default= is supplied.. In your example you probably want nargs='?', not '*'.
msg386799 - (view) Author: Clint Olsen (Clint Olsen) Date: 2021-02-10 19:14
Do you think it's unreasonable/unintuitive to infer nargs from the default value?
msg386801 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2021-02-10 19:19
Would you infer it to be ‘?’ or ‘*’?

I’m not completely opposed, but I haven’t thought it through or looked at the code.
msg386802 - (view) Author: Clint Olsen (Clint Olsen) Date: 2021-02-10 19:30
I think your original suggestion makes sense: '?'

My intuition is that nargs helps argparse discern whether it's dealing with single or multiple values. That may not be what was intended.

There probably shouldn't be multiple ways of indicating an argument is optional. It seems that required= and nargs='?'|'*' combinations could lead to conflicting requirements.
msg392957 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2021-05-04 21:13
To a large degree the Action parameters operate independently.  That is, different parts of the code use the parameters for their own purposes. The composite behavior is a result of those individual actors, rather than some sort of overall coordinated plan.

First your Action is 'positional' (no flag string), and "store" type.  nargs is the default None.  The only extra is the default value (the default default is None).

_ActionsContainer._get_positional_kwargs processes such an argument, setting the 'required' parameter with:

        # mark positional arguments as required if at least one is
        # always required
        if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]:
            kwargs['required'] = True
        if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs:
            kwargs['required'] = True

At the start of parsing, all 'default' values are added to the 'args' Namespace.

If an Action is "seen", either via the appropriate flag string, or a positional value, it is added to the "seen_actions" list.

At the end of parsing, it iterates through all the '_actions'.  If it is not in the 'seen_actions' list, and is marked "required", it gets added to the 'reqiuired_action' error list.

So even though your Action has a 'default' it is still required.  The default is, in effect, ignored.  The nargs value (default or not) has priority in setting the 'required' value.

'?/*' nargs require special handling.  This nargs requirement is satisfied by an empty list.  Such a positional will, in effect, always be seen. But if a explicit default is provided, that will over write the empty list. 

Handling defaults is inherently tricky.  But except for the special '?/*' case, specifying a 'default' with a positional doesn't make much sense.
msg392965 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2021-05-04 23:03
> Specifying a 'default' with a positional doesn't make much sense.

That was my thought as well.  Perhaps that is worth a note in the docs but there is likely no real need to change the behavior.
History
Date User Action Args
2022-04-11 14:59:41adminsetgithub: 87358
2021-05-04 23:03:59rhettingersetnosy: + rhettinger
messages: + msg392965
2021-05-04 21:13:34paul.j3setnosy: + paul.j3
messages: + msg392957
2021-02-10 19:30:28Clint Olsensetmessages: + msg386802
2021-02-10 19:19:01eric.smithsetmessages: + msg386801
2021-02-10 19:14:39Clint Olsensetmessages: + msg386799
2021-02-10 14:27:41eric.smithsetnosy: + eric.smith
messages: + msg386781
2021-02-10 10:26:12Clint Olsensetmessages: + msg386771
2021-02-10 10:24:37Clint Olsencreate