https://github.com/python/cpython/commit/a8c420879e344a365ec4542107db5c4830ef2520
commit: a8c420879e344a365ec4542107db5c4830ef2520
branch: 3.15
author: Savannah Ostrowski <[email protected]>
committer: savannahostrowski <[email protected]>
date: 2026-05-11T18:25:40Z
summary:

[3.15] gh-149614 - Restore deepcopiability of argparse.ArgumentParser… (#149693)

[3.15] gh-149614 - Restore deepcopiability of argparse.ArgumentParser instances 
(GH-149617)
(cherry picked from commit fadd9bc14e43041c84bb8d06824990264fe1434a)

Co-authored-by: David Ellis <[email protected]>
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>

files:
A Misc/NEWS.d/next/Library/2026-05-09-21-02-08.gh-issue-149614.U4snj3.rst
M Lib/argparse.py
M Lib/test/test_argparse.py

diff --git a/Lib/argparse.py b/Lib/argparse.py
index 6d21823e652429..29e6ebb9634261 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -163,6 +163,8 @@ class _ColorlessTheme:
     def __getattr__(self, name):
         # _colorize's no_color themes are just all empty strings
         # by directly using empty strings the import is avoided
+        if name.startswith("_"):
+            raise AttributeError(name)
         return ""
 
 _colorless_theme = _ColorlessTheme()
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index 4ea5b6f53a0426..1dc3f538f4ad8b 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -140,6 +140,48 @@ def test_parse_args(self):
         )
 
 
+class TestArgumentParserCopiable(unittest.TestCase):
+    def _get_parser(self):
+        parser = argparse.ArgumentParser(exit_on_error=False)
+        parser.add_argument('--foo', type=int, default=42)
+        parser.add_argument('bar', nargs='?', default='baz')
+        return parser
+
+    @force_not_colorized
+    def test_copiable(self):
+        import copy
+        parser = self._get_parser()
+        parser2 = copy.copy(parser)
+        ns = parser2.parse_args(['--foo', '123', 'quux'])
+        self.assertEqual(ns.foo, 123)
+        self.assertEqual(ns.bar, 'quux')
+        ns2 = parser2.parse_args([])
+        self.assertEqual(ns2.foo, 42)
+        self.assertEqual(ns2.bar, 'baz')
+
+        # Test shallow copy also gets new arguments
+        parser.add_argument("--extra")
+        ns3 = parser2.parse_args(["--extra", "bar"])
+        self.assertEqual(ns3.extra, "bar")
+
+    @force_not_colorized
+    def test_deepcopiable(self):
+        import copy
+        parser = self._get_parser()
+        parser2 = copy.deepcopy(parser)
+        ns = parser2.parse_args(['--foo', '123', 'quux'])
+        self.assertEqual(ns.foo, 123)
+        self.assertEqual(ns.bar, 'quux')
+        ns2 = parser2.parse_args([])
+        self.assertEqual(ns2.foo, 42)
+        self.assertEqual(ns2.bar, 'baz')
+
+        # Test deep copy does not get new arguments
+        parser.add_argument("--extra")
+        with self.assertRaises(argparse.ArgumentError):
+            parser2.parse_args(["--extra", "bar"])
+
+
 class TestArgumentParserPickleable(unittest.TestCase):
 
     @force_not_colorized
@@ -7863,12 +7905,25 @@ def fake_can_colorize(*, file=None):
 
     def test_fake_color_theme_matches_real(self):
         from argparse import _colorless_theme
+
+        # Check the attributes match those of the 'real' theme
         _colorize_nocolor = _colorize.get_theme(force_no_color=True).argparse
         for k in _colorize_nocolor:
             self.assertEqual(
                 getattr(_colorless_theme, k), getattr(_colorize_nocolor, k)
             )
 
+    def test_fake_color_theme_raises(self):
+        from argparse import _colorless_theme
+
+        # Make sure the _colorless_theme doesn't return empty strings
+        # for magic methods or private attributes
+        with self.assertRaises(AttributeError):
+            _colorless_theme.__unknown_dunder__
+
+        with self.assertRaises(AttributeError):
+            _colorless_theme._private_attribute
+
 
 class TestModule(unittest.TestCase):
     def test_deprecated__version__(self):
diff --git 
a/Misc/NEWS.d/next/Library/2026-05-09-21-02-08.gh-issue-149614.U4snj3.rst 
b/Misc/NEWS.d/next/Library/2026-05-09-21-02-08.gh-issue-149614.U4snj3.rst
new file mode 100644
index 00000000000000..5169c6c203fc1b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-05-09-21-02-08.gh-issue-149614.U4snj3.rst
@@ -0,0 +1 @@
+Fix a regression that broke the ability to deepcopy 
:class:`argparse.ArgumentParser` instances.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to