https://github.com/python/cpython/commit/d5e28e19ae75465b563b5f88acf8accc92545470
commit: d5e28e19ae75465b563b5f88acf8accc92545470
branch: main
author: Bernát Gábor <[email protected]>
committer: savannahostrowski <[email protected]>
date: 2026-10-21T16:27:38Z
summary:

gh-144812: Make argparse group classes public (#158045)

files:
A Misc/NEWS.d/next/Library/2026-09-23-23-34-30.gh-issue-144812.ujptbVOI.rst
M Doc/library/argparse.rst
M Doc/whatsnew/3.16.rst
M Lib/argparse.py
M Lib/test/test_argparse.py

diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst
index 658209bc55fa14..3a8eab76ca0e2b 100644
--- a/Doc/library/argparse.rst
+++ b/Doc/library/argparse.rst
@@ -2072,6 +2072,19 @@ Argument groups
       is now deprecated.
 
 
+.. class:: ArgumentGroup
+
+   The type of objects returned by :meth:`ArgumentParser.add_argument_group`.
+   Its :meth:`~ArgumentParser.add_argument` and
+   :meth:`~ArgumentParser.add_mutually_exclusive_group` methods work as on
+   :class:`ArgumentParser`.  Use :meth:`~ArgumentParser.add_argument_group`
+   to create instances.  Only the methods documented here are part of the
+   public API; other attributes and methods are implementation details.
+
+   .. versionadded:: next
+      The private ``_ArgumentGroup`` name remains as an alias.
+
+
 Mutual exclusion
 ^^^^^^^^^^^^^^^^
 
@@ -2135,6 +2148,20 @@ Mutual exclusion
       exposed through inheritance.
 
 
+.. class:: MutuallyExclusiveGroup
+
+   The type of objects returned by
+   :meth:`ArgumentParser.add_mutually_exclusive_group`.
+   Its :meth:`~ArgumentParser.add_argument` method works as on
+   :class:`ArgumentParser`.  Use
+   :meth:`~ArgumentParser.add_mutually_exclusive_group` to create instances.
+   Only the ``add_argument`` method documented here is part of the public API;
+   other attributes and methods are implementation details.
+
+   .. versionadded:: next
+      The private ``_MutuallyExclusiveGroup`` name remains as an alias.
+
+
 Parser defaults
 ^^^^^^^^^^^^^^^
 
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index c7ba9235a1a326..b29170b5b0b93d 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -126,6 +126,16 @@ New modules
 Improved modules
 ================
 
+argparse
+--------
+
+* :class:`~argparse.ArgumentGroup` and
+  :class:`~argparse.MutuallyExclusiveGroup`, the types returned by
+  :meth:`~argparse.ArgumentParser.add_argument_group` and
+  :meth:`~argparse.ArgumentParser.add_mutually_exclusive_group`, are now
+  public. The ``_``-prefixed names remain as aliases.
+  (Contributed by Bernát Gábor in :gh:`144812`.)
+
 asyncio
 -------
 
diff --git a/Lib/argparse.py b/Lib/argparse.py
index 4cf5dca148c969..c01a6351f14482 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -67,6 +67,7 @@
 __all__ = [
     'ArgumentParser',
     'ArgumentError',
+    'ArgumentGroup',
     'ArgumentTypeError',
     'BooleanOptionalAction',
     'FileType',
@@ -75,6 +76,7 @@
     'RawDescriptionHelpFormatter',
     'RawTextHelpFormatter',
     'MetavarTypeHelpFormatter',
+    'MutuallyExclusiveGroup',
     'Namespace',
     'Action',
     'ONE_OR_MORE',
@@ -1699,12 +1701,12 @@ def add_argument(self, *args, **kwargs):
         return self._add_action(action)
 
     def add_argument_group(self, *args, **kwargs):
-        group = _ArgumentGroup(self, *args, **kwargs)
+        group = ArgumentGroup(self, *args, **kwargs)
         self._action_groups.append(group)
         return group
 
     def add_mutually_exclusive_group(self, **kwargs):
-        group = _MutuallyExclusiveGroup(self, **kwargs)
+        group = MutuallyExclusiveGroup(self, **kwargs)
         self._mutually_exclusive_groups.append(group)
         return group
 
@@ -1892,7 +1894,7 @@ def _check_help(self, action):
                 raise ValueError('badly formed help string') from exc
 
 
-class _ArgumentGroup(_ActionsContainer):
+class ArgumentGroup(_ActionsContainer):
 
     def __init__(self, container, title=None, description=None, **kwargs):
         if 'prefix_chars' in kwargs:
@@ -1907,7 +1909,7 @@ def __init__(self, container, title=None, 
description=None, **kwargs):
         update('conflict_handler', container.conflict_handler)
         update('prefix_chars', container.prefix_chars)
         update('argument_default', container.argument_default)
-        super_init = super(_ArgumentGroup, self).__init__
+        super_init = super(ArgumentGroup, self).__init__
         super_init(description=description, **kwargs)
 
         # group attributes
@@ -1924,21 +1926,21 @@ def __init__(self, container, title=None, 
description=None, **kwargs):
         self._mutually_exclusive_groups = container._mutually_exclusive_groups
 
     def _add_action(self, action):
-        action = super(_ArgumentGroup, self)._add_action(action)
+        action = super(ArgumentGroup, self)._add_action(action)
         self._group_actions.append(action)
         return action
 
     def _remove_action(self, action):
-        super(_ArgumentGroup, self)._remove_action(action)
+        super(ArgumentGroup, self)._remove_action(action)
         self._group_actions.remove(action)
 
     def add_argument_group(self, *args, **kwargs):
         raise ValueError('argument groups cannot be nested')
 
-class _MutuallyExclusiveGroup(_ArgumentGroup):
+class MutuallyExclusiveGroup(ArgumentGroup):
 
     def __init__(self, container, required=False):
-        super(_MutuallyExclusiveGroup, self).__init__(container)
+        super(MutuallyExclusiveGroup, self).__init__(container)
         self.required = required
         self._container = container
 
@@ -1957,6 +1959,10 @@ def _remove_action(self, action):
     def add_mutually_exclusive_group(self, **kwargs):
         raise ValueError('mutually exclusive groups cannot be nested')
 
+# Kept for code that imports the pre-3.16 private names.
+_ArgumentGroup = ArgumentGroup
+_MutuallyExclusiveGroup = MutuallyExclusiveGroup
+
 def _prog_name(prog=None):
     if prog is not None:
         return prog
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index 3abdb3244570a5..e1c4cf6561fc77 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -3246,6 +3246,20 @@ def test_nested_argument_group(self):
                                  'argument groups cannot be nested',
                                  g.add_argument_group)
 
+    def test_group_types(self):
+        parser = argparse.ArgumentParser()
+        group = parser.add_argument_group()
+        self.assertIs(type(group), argparse.ArgumentGroup)
+        self.assertIs(type(parser.add_mutually_exclusive_group()),
+                      argparse.MutuallyExclusiveGroup)
+        self.assertIs(type(group.add_mutually_exclusive_group()),
+                      argparse.MutuallyExclusiveGroup)
+
+    def test_private_aliases(self):
+        self.assertIs(argparse._ArgumentGroup, argparse.ArgumentGroup)
+        self.assertIs(argparse._MutuallyExclusiveGroup,
+                      argparse.MutuallyExclusiveGroup)
+
 # ===================
 # Parent parser tests
 # ===================
diff --git 
a/Misc/NEWS.d/next/Library/2026-09-23-23-34-30.gh-issue-144812.ujptbVOI.rst 
b/Misc/NEWS.d/next/Library/2026-09-23-23-34-30.gh-issue-144812.ujptbVOI.rst
new file mode 100644
index 00000000000000..d24f028fdbeaae
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-23-23-34-30.gh-issue-144812.ujptbVOI.rst
@@ -0,0 +1,3 @@
+Make :class:`argparse.ArgumentGroup` and
+:class:`argparse.MutuallyExclusiveGroup` public. The private
+``_ArgumentGroup`` and ``_MutuallyExclusiveGroup`` names remain as aliases.

_______________________________________________
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