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 mutually exclusive group does not exclude in some cases #85531
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=<Date2020-07-21.11:46:47.665>labels= ['type-bug', 'library', '3.10']
title='argparse mutually exclusive group does not exclude in some cases'updated_at=<Date2020-07-21.17:08:13.980>user='https://bugs.python.org/kkarbowiak'
The documentation for ArgumentParser.add_mutually_exclusive_group states: "argparse will make sure that only one of the arguments in the mutually exclusive group was present on the command line".
This is not the case in certain circumstances:
importargparseparser=argparse.ArgumentParser()
group=parser.add_mutually_exclusive_group()
group.add_argument('-a')
group.add_argument('-b', nargs='?')
parser.parse_args('-a a -b'.split())
The above code does not produce any error, even though both exclusive arguments are present.
My guess is that the check for mutual exclusion is not done during processing of each command line argument, but rather afterwards. It seems the check only ensures at most one argument from group is not None.
The issue exists at least on Python 2.7.13, 3.6, 3.7.5, 3.8, and 3.10.
This is the result of how default values are handled with '?' (and '*') nargs.
At the start of nested take_action function (which handles all actions) is this code:
argument_values=self._get_values(action, argument_strings)
# error if this argument is not allowed with other previously# seen arguments, assuming that actions that use the default# value don't really count as "present"ifargument_valuesisnotaction.default:
seen_non_default_actions.add(action)
forconflict_actioninaction_conflicts.get(action, []):
ifconflict_actioninseen_non_default_actions:
msg=_('not allowed with argument %s')
action_name=_get_action_name(conflict_action)
raiseArgumentError(action, msg%action_name)
'get_values' gets the values for this action. A bare '-b' will be given its 'default'.
An optional positional is always 'seen', since an empty list satisfies its 'nargs'. 'get_values' assigns the default instead. This code in take_action allows us to include such positionals in a mutually exclusive group.
This handing of the '?' optional is a byproduct of that code. Normally we provide both a 'default' and a 'const' with such an argument, giving us a 3-way switch (default, const or user-value). If either 'default' or 'const' is provided, your '-b' will behave as expected.
I have a feeling that trying to document this edge case will just make things more confusing.
Activity
kkarbowiak commentedon Jul 21, 2020
The documentation for
ArgumentParser.add_mutually_exclusive_groupstates: "argparse will make sure that only one of the arguments in the mutually exclusive group was present on the command line".This is not the case in certain circumstances:
The above code does not produce any error, even though both exclusive arguments are present.
My guess is that the check for mutual exclusion is not done during processing of each command line argument, but rather afterwards. It seems the check only ensures at most one argument from group is not
None.The issue exists at least on Python 2.7.13, 3.6, 3.7.5, 3.8, and 3.10.
paulj3 commentedon Jul 21, 2020
This is the result of how default values are handled with '?' (and '*') nargs.
At the start of nested
take_actionfunction (which handles all actions) is this code:'get_values' gets the values for this action. A bare '-b' will be given its 'default'.
An optional positional is always 'seen', since an empty list satisfies its 'nargs'. 'get_values' assigns the default instead. This code in take_action allows us to include such positionals in a mutually exclusive group.
This handing of the '?' optional is a byproduct of that code. Normally we provide both a 'default' and a 'const' with such an argument, giving us a 3-way switch (default, const or user-value). If either 'default' or 'const' is provided, your '-b' will behave as expected.
I have a feeling that trying to document this edge case will just make things more confusing.
Add test for pythongh-85531.
serhiy-storchaka commentedon Sep 21, 2024
This looks similar to #63143, and #124307 fixes also this issue.