Title: Argparse: Explicit default required arguments with add_mutually_exclusive_group are rejected
Type: Stage: patch review
Components: Library (Lib) Versions: Python 3.10, Python 3.9, Python 3.8
Status: open Resolution:
Dependencies: Superseder:
Assigned To: rhettinger Nosy List: keith, paul.j3, rhettinger
Priority: normal Keywords: patch

Created on 2021-02-14 06:34 by keith, last changed 2021-04-07 16:24 by paul.j3.

Pull Requests
URL Status Linked Edit
PR 24526 open keith, 2021-02-14 06:37
Messages (5)
msg386934 - (view) Author: Keith Smiley (keith) * Date: 2021-02-14 06:34
With this code:

import argparse

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--foo", default="1")
args = parser.parse_args()

When you explicitly pass `--foo 1`, it is treated as if no argument was passed:

% python3 /tmp/ --foo 1
usage: [-h] (--foo FOO | --bar BAR) error: one of the arguments --foo --bar is required

I can't tell if this behavior is intentional, but it was surprising to me. It also seems to be somewhat based on the length of the default string. For example on my macOS machine if I change the default to `longerstring` it does not have this issue.
msg386959 - (view) Author: Keith Smiley (keith) * Date: 2021-02-14 18:15
Here's an example outside of argparse showing this is caused by the `is` comparison with interned string:

import sys

short_string = sys.argv[1]
short_default = '1'
long_string = sys.argv[2]
long_default = 'not-interned'

print(f"short comparisons: id1: {id(short_default)} id2: {id(short_string)}, eq: {short_default == short_string}, is: {short_default is short_string}")
print(f"long comparisons: id1: {id(long_default)} id2: {id(long_string)}, eq: {long_default == long_string}, is: {long_default is long_string}")

% ./python.exe /tmp/ 1 not-interned
short comparisons: id1: 4523386416 id2: 4523386416, eq: True, is: True
long comparisons: id1: 4524440064 id2: 4523395296, eq: True, is: False
msg390398 - (view) Author: Keith Smiley (keith) * Date: 2021-04-07 03:37
Would someone be able to review this change?
msg390406 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2021-04-07 09:00
I'll review this but it may take a little while to get to it.
msg390443 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2021-04-07 16:24
An overlapping issue
argparse: default args in mutually exclusive groups

That issue shows that this problem arises with small integers as well (<257), which in cpython have unique ids. It's an implementation detail, pypy for example does not have this issue.

The whole purpose of this extra default testing is to allow '?/*' positionals in mutually_exclusive_groups.

The patch I proposed in 2013 is basically the same thing, except I called the new flag variable 'using_default'.

We should review the discussion in that issue to see if it raises any additional issues or concerns.
Date User Action Args
2021-04-07 16:24:24paul.j3setmessages: + msg390443
2021-04-07 09:00:24rhettingersetassignee: rhettinger

messages: + msg390406
nosy: + rhettinger, paul.j3
2021-04-07 03:37:52keithsetmessages: + msg390398
2021-02-15 15:16:30iritkatrielsettitle: Explicit default required arguments with add_mutually_exclusive_group are rejected -> Argparse: Explicit default required arguments with add_mutually_exclusive_group are rejected
versions: - Python 3.6, Python 3.7
2021-02-14 18:15:58keithsetmessages: + msg386959
2021-02-14 06:37:39keithsetkeywords: + patch
stage: patch review
pull_requests: + pull_request23311
2021-02-14 06:34:45keithcreate