From 93406f1959874bf6309eedddf779c521d3219ee4 Mon Sep 17 00:00:00 2001 From: Dmitry Voropaev Date: Fri, 25 Sep 2026 11:40:11 +0300 Subject: [PATCH 1/5] gh-110131: Document argparse's extend action with a non-empty default The 'append' bullet says that a non-empty list default is kept and that command-line values land after it. 'extend' works the same way for an option, but the docs say nothing about it, which is the confusion reported in gh-110131. The sentence is scoped to the option deliberately. For a positional with nargs='*' or '?' and no command-line values, _get_values hands the default back as the parsed value while the same default is already in the namespace, so the action applies it twice and extend(default=['X']) yields ['X', 'X']. The unqualified wording would be wrong there. --- Doc/library/argparse.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index fc5302d875fc1f..4518ecc476b047 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -818,6 +818,9 @@ how the command-line arguments should be handled. The supplied actions are: value ``'+'`` or ``'*'``. Note that when nargs_ is ``None`` (the default) or ``'?'``, each character of the argument string will be appended to the list. + If the default value is a non-empty list, the parsed value for the option + will start with the default list's elements and any values from the + command line will be appended after those default values. Example usage:: >>> parser = argparse.ArgumentParser() From 94bd220a0fb89007a41ff6c9601c0f8536213433 Mon Sep 17 00:00:00 2001 From: Dmitry Voropaev Date: Fri, 25 Sep 2026 11:40:35 +0300 Subject: [PATCH 2/5] gh-110131: Test argparse's extend action with a non-empty default TestOptionalsActionAppendWithDefault covers this for 'append'; there was no equivalent for 'extend', so nothing pinned the behavior the docs now describe. The coverage is limited to an option, matching the scope of the new sentence. --- Lib/test/test_argparse.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 75beb5ede13fef..d1bbc2434f1495 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2470,6 +2470,21 @@ class TestActionExtend(ParserTestCase): ] +class TestActionExtendWithDefault(ParserTestCase): + """Tests the extend action for an Optional with a non-empty default""" + + argument_signatures = [ + Sig('--baz', action='extend', nargs='+', default=['X']), + ] + failures = ['a', '--baz', 'a --baz'] + successes = [ + ('', NS(baz=['X'])), + ('--baz a', NS(baz=['X', 'a'])), + ('--baz a b', NS(baz=['X', 'a', 'b'])), + ('--baz a --baz b c', NS(baz=['X', 'a', 'b', 'c'])), + ] + + class TestNegativeNumber(ParserTestCase): """Test parsing negative numbers""" From 5f7766cb0c71689aa698c212b9eeaaa3f74ed4cd Mon Sep 17 00:00:00 2001 From: Dmitry Voropaev Date: Fri, 25 Sep 2026 11:40:55 +0300 Subject: [PATCH 3/5] gh-110131: Restore the "for the option" scope in argparse's append docs Before GH-131389 the bullet read "the default elements will be present in the parsed value for the option". That copyedit rewrote the sentence and added an example, and the qualifier went with it, which left the bullet claiming something that does not hold for positionals: append with nargs='*' or '?' and default=['X'] gives ['X', ['X']] on empty input, because the default is applied twice. gh-110131 is filed against both 'append' and 'extend', so put the scope back rather than leave the two bullets disagreeing about it. --- Doc/library/argparse.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 4518ecc476b047..fc724240f5f492 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -791,9 +791,9 @@ how the command-line arguments should be handled. The supplied actions are: * ``'append'`` - This appends each argument value to a list. It is useful for allowing an option to be specified multiple times. - If the default value is a non-empty list, the parsed value will start - with the default list's elements and any values from the command line - will be appended after those default values. Example usage:: + If the default value is a non-empty list, the parsed value for the option + will start with the default list's elements and any values from the + command line will be appended after those default values. Example usage:: >>> parser = argparse.ArgumentParser() >>> parser.add_argument('--foo', action='append', default=['0']) From 02246d2c6cba9ce55aaa5eea2c1b54c386baa1b2 Mon Sep 17 00:00:00 2001 From: Dmitry Voropaev Date: Fri, 25 Sep 2026 18:59:02 +0300 Subject: [PATCH 4/5] gh-110131: Drop the test, show a non-empty default in the example --- Doc/library/argparse.rst | 5 +++++ Lib/test/test_argparse.py | 15 --------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index fc724240f5f492..8a47a1c6131d6e 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -827,6 +827,11 @@ how the command-line arguments should be handled. The supplied actions are: >>> parser.add_argument("--foo", action="extend", nargs="+", type=str) >>> parser.parse_args(["--foo", "f1", "--foo", "f2", "f3", "f4"]) Namespace(foo=['f1', 'f2', 'f3', 'f4']) + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument("--foo", action="extend", nargs="+", type=str, + ... default=["d1"]) + >>> parser.parse_args(["--foo", "f1", "f2"]) + Namespace(foo=['d1', 'f1', 'f2']) .. versionadded:: 3.8 diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index d1bbc2434f1495..75beb5ede13fef 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2470,21 +2470,6 @@ class TestActionExtend(ParserTestCase): ] -class TestActionExtendWithDefault(ParserTestCase): - """Tests the extend action for an Optional with a non-empty default""" - - argument_signatures = [ - Sig('--baz', action='extend', nargs='+', default=['X']), - ] - failures = ['a', '--baz', 'a --baz'] - successes = [ - ('', NS(baz=['X'])), - ('--baz a', NS(baz=['X', 'a'])), - ('--baz a b', NS(baz=['X', 'a', 'b'])), - ('--baz a --baz b c', NS(baz=['X', 'a', 'b', 'c'])), - ] - - class TestNegativeNumber(ParserTestCase): """Test parsing negative numbers""" From 844bb6313a9f0782a5a8aff2d59c31c8d5bcbe24 Mon Sep 17 00:00:00 2001 From: Dmitry Voropaev Date: Sat, 26 Sep 2026 03:09:39 +0300 Subject: [PATCH 5/5] gh-110131: Keep one extend example --- Doc/library/argparse.rst | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 8a47a1c6131d6e..23107fefe96524 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -823,15 +823,11 @@ how the command-line arguments should be handled. The supplied actions are: command line will be appended after those default values. Example usage:: - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument("--foo", action="extend", nargs="+", type=str) - >>> parser.parse_args(["--foo", "f1", "--foo", "f2", "f3", "f4"]) - Namespace(foo=['f1', 'f2', 'f3', 'f4']) >>> parser = argparse.ArgumentParser() >>> parser.add_argument("--foo", action="extend", nargs="+", type=str, ... default=["d1"]) - >>> parser.parse_args(["--foo", "f1", "f2"]) - Namespace(foo=['d1', 'f1', 'f2']) + >>> parser.parse_args(["--foo", "f1", "--foo", "f2", "f3", "f4"]) + Namespace(foo=['d1', 'f1', 'f2', 'f3', 'f4']) .. versionadded:: 3.8