https://github.com/python/cpython/commit/11503211c6e8985a305f8e2a8530e2258f982180
commit: 11503211c6e8985a305f8e2a8530e2258f982180
branch: main
author: Jelle Zijlstra <jelle.zijls...@gmail.com>
committer: JelleZijlstra <jelle.zijls...@gmail.com>
date: 2025-07-28T12:35:40-07:00
summary:

gh-136843: Document how multiple inheritance works (#136844)

Co-authored-by: Petr Viktorin <encu...@gmail.com>
Co-authored-by: Akuli <akuviljane...@gmail.com>

files:
M Doc/reference/compound_stmts.rst
M Doc/reference/datamodel.rst
M Doc/tutorial/classes.rst

diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst
index a416cbb4cc8eab..7ac4d8587ce7d5 100644
--- a/Doc/reference/compound_stmts.rst
+++ b/Doc/reference/compound_stmts.rst
@@ -1421,6 +1421,9 @@ is equivalent to ::
    class Foo(object):
        pass
 
+There may be one or more base classes; see :ref:`multiple-inheritance` below 
for more
+information.
+
 The class's suite is then executed in a new execution frame (see 
:ref:`naming`),
 using a newly created local namespace and the original global namespace.
 (Usually, the suite contains mostly function definitions.)  When the class's
@@ -1490,6 +1493,119 @@ can be used to create instance variables with different 
implementation details.
       were introduced in :pep:`318`.
 
 
+.. _multiple-inheritance:
+
+Multiple inheritance
+--------------------
+
+Python classes may have multiple base classes, a technique known as
+*multiple inheritance*.  The base classes are specified in the class definition
+by listing them in parentheses after the class name, separated by commas.
+For example, the following class definition:
+
+.. doctest::
+
+   >>> class A: pass
+   >>> class B: pass
+   >>> class C(A, B): pass
+
+defines a class ``C`` that inherits from classes ``A`` and ``B``.
+
+The :term:`method resolution order` (MRO) is the order in which base classes 
are
+searched when looking up an attribute on a class. See :ref:`python_2.3_mro` 
for a
+description of how Python determines the MRO for a class.
+
+Multiple inheritance is not always allowed. Attempting to define a class with 
multiple
+inheritance will raise an error if one of the bases does not allow 
subclassing, if a consistent MRO
+cannot be created, if no valid metaclass can be determined, or if there is an 
instance
+layout conflict. We'll discuss each of these in turn.
+
+First, all base classes must allow subclassing. While most classes allow 
subclassing,
+some built-in classes do not, such as :class:`bool`:
+
+.. doctest::
+
+   >>> class SubBool(bool):  # TypeError
+   ...    pass
+   Traceback (most recent call last):
+      ...
+   TypeError: type 'bool' is not an acceptable base type
+
+In the resolved MRO of a class, the class's bases appear in the order they were
+specified in the class's bases list. Additionally, the MRO always lists a child
+class before any of its bases. A class definition will fail if it is 
impossible to
+resolve a consistent MRO that satisfies these rules from the list of bases 
provided:
+
+.. doctest::
+
+   >>> class Base: pass
+   >>> class Child(Base): pass
+   >>> class Grandchild(Base, Child): pass  # TypeError
+   Traceback (most recent call last):
+      ...
+   TypeError: Cannot create a consistent method resolution order (MRO) for 
bases Base, Child
+
+In the MRO of ``Grandchild``, ``Base`` must appear before ``Child`` because it 
is first
+in the base class list, but it must also appear after ``Child`` because it is 
a parent of
+``Child``. This is a contradiction, so the class cannot be defined.
+
+If some of the bases have a custom :term:`metaclass`, the metaclass of the 
resulting class
+is chosen among the metaclasses of the bases and the explicitly specified 
metaclass of the
+child class. It must be a metaclass that is a subclass of
+all other candidate metaclasses. If no such metaclass exists among the 
candidates,
+the class cannot be created, as explained in :ref:`metaclass-determination`.
+
+Finally, the instance layouts of the bases must be compatible. This means that 
it must be
+possible to compute a *solid base* for the class. Exactly which classes are 
solid bases
+depends on the Python implementation.
+
+.. impl-detail::
+
+   In CPython, a class is a solid base if it has a
+   nonempty :attr:`~object.__slots__` definition.
+   Many but not all classes defined in C are also solid bases, including most
+   builtins (such as :class:`int` or :class:`BaseException`)
+   but excluding most concrete :class:`Exception` classes. Generally, a C class
+   is a solid base if its underlying struct is different in size from its base 
class.
+
+Every class has a solid base. :class:`object`, the base class, has itself as 
its solid base.
+If there is a single base, the child class's solid base is that class if it is 
a solid base,
+or else the base class's solid base. If there are multiple bases, we first 
find the solid base
+for each base class to produce a list of candidate solid bases. If there is a 
unique solid base
+that is a subclass of all others, then that class is the solid base. 
Otherwise, class creation
+fails.
+
+Example:
+
+.. doctest::
+
+   >>> class Solid1:
+   ...    __slots__ = ("solid1",)
+   >>>
+   >>> class Solid2:
+   ...    __slots__ = ("solid2",)
+   >>>
+   >>> class SolidChild(Solid1):
+   ...    __slots__ = ("solid_child",)
+   >>>
+   >>> class C1:  # solid base is `object`
+   ...    pass
+   >>>
+   >>> # OK: solid bases are `Solid1` and `object`, and `Solid1` is a subclass 
of `object`.
+   >>> class C2(Solid1, C1):  # solid base is `Solid1`
+   ...    pass
+   >>>
+   >>> # OK: solid bases are `SolidChild` and `Solid1`, and `SolidChild` is a 
subclass of `Solid1`.
+   >>> class C3(SolidChild, Solid1):  # solid base is `SolidChild`
+   ...    pass
+   >>>
+   >>> # Error: solid bases are `Solid1` and `Solid2`, but neither is a 
subclass of the other.
+   >>> class C4(Solid1, Solid2):  # error: no single solid base
+   ...    pass
+   Traceback (most recent call last):
+     ...
+   TypeError: multiple bases have instance lay-out conflict
+
 .. _async:
 
 Coroutines
diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst
index 4a099e81daccb3..7af3457070b84a 100644
--- a/Doc/reference/datamodel.rst
+++ b/Doc/reference/datamodel.rst
@@ -2629,7 +2629,7 @@ Notes on using *__slots__*:
 * :attr:`~object.__class__` assignment works only if both classes have the
   same *__slots__*.
 
-* :ref:`Multiple inheritance <tut-multiple>` with multiple slotted parent
+* :ref:`Multiple inheritance <multiple-inheritance>` with multiple slotted 
parent
   classes can be used,
   but only one parent is allowed to have attributes created by slots
   (the other bases must have empty slot layouts) - violations raise
@@ -2779,6 +2779,8 @@ Resolving MRO entries
       Core support for typing module and generic types.
 
 
+.. _metaclass-determination:
+
 Determining the appropriate metaclass
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. index::
diff --git a/Doc/tutorial/classes.rst b/Doc/tutorial/classes.rst
index 9d0fab8861d2a9..fa964271d79bd8 100644
--- a/Doc/tutorial/classes.rst
+++ b/Doc/tutorial/classes.rst
@@ -663,6 +663,9 @@ Taken together, these properties make it possible to design 
reliable and
 extensible classes with multiple inheritance.  For more detail, see
 :ref:`python_2.3_mro`.
 
+In some cases multiple inheritance is not allowed; see 
:ref:`multiple-inheritance`
+for details.
+
 
 .. _tut-private:
 

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: arch...@mail-archive.com

Reply via email to