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.

Author paul.j3
Recipients bethard, mblahay, paul.j3, rgov
Date 2019-05-11.02:24:22
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1557541463.94.0.43734904598.issue35495@roundup.psfhosted.org>
In-reply-to
Content
At the start of parse_known_args, all defaults (except SUPPRESS ones) are placed in the namespace:

        # add any action defaults that aren't present
        for action in self._actions:
            if action.dest is not SUPPRESS:
                if not hasattr(namespace, action.dest):
                    if action.default is not SUPPRESS:
                        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:

    setattr(namespace, action.dest,
        self._get_value(action, action.default))

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 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

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:

        if kwargs.get('nargs') == REMAINDER and 'default' in kwargs:
            msg = _("'default' is not allowed with a REMAINDER positional")
            raise TypeError(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.

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.
History
Date User Action Args
2019-05-11 02:24:23paul.j3setrecipients: + paul.j3, bethard, rgov, mblahay
2019-05-11 02:24:23paul.j3setmessageid: <1557541463.94.0.43734904598.issue35495@roundup.psfhosted.org>
2019-05-11 02:24:23paul.j3linkissue35495 messages
2019-05-11 02:24:22paul.j3create