Skip to content

gh-136843: Document how multiple inheritance works #136844

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 28, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions Doc/reference/compound_stmts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1490,6 +1493,97 @@ 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::

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 is invalid, if a consistent MRO
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about "one of the bases is invalid" part. Some class is not acceptable to be base class. Is it correct to call this class "invalid"? I'd suggest little rewording.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I was also thinking of trying to inherit from something that isn't a class. I'll reword.

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`::

class SubBool(bool): # TypeError
pass

To create a consistent MRO, all bases appear in the order
they were specified in the base class list and every child class must appear before its
base classes. Below is an example where this fails::

class Base: pass
class Child(Base): pass
class Grandchild(Base, Child): pass # TypeError

In the MRO of ``Grandchild``, ``Child`` must appear before ``Base`` because it is first
in the base class list, but it must also appear after ``Base`` because it is a child of
``Base``. 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. It must be a metaclass that is a subclass of
all other candidate metaclasses. If no such metaclass exists, the class cannot be created,
as explained in :ref:`metaclass-determination`.

Finally, the memory layouts of the bases must be compatible. This means that it must be
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it "memory layout" or "instance layout"? Or those are fully equivalent? This is not very obvious now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We debated how to describe this concept while writing the docs for the relevant ty rule. I think the clearest way to describe the concept is "the memory layout instances of the class will have", but that's a bit wordy 😄 "instance memory layout" is probably next best?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll go with "instance layout" since that's what the TypeError says.

possible to compute a *solid base* for the class. A class is a solid base if it has a
nonempty :attr:`~object.__slots__` definition; some other classes may also be solid bases,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also consider the __slots__ behaviour a CPython implementation detail?
IMO, it would be good if alternate implementations -- or even future versions of CPython -- could drop the limitation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, but this behavior is already documented in the __slots__ docs without any "implementation detail" warning.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That note was added in #1819; it looks like a note on current behaviour rather than documenting design intent.
IMO, several of the notes are simply implementation limitations that we should be allowed to lift if the internals change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the mention of __slots__ into the implementation detail section.

depending on the Python implementation.

.. impl-detail::

In CPython, many but not all classes defined in C are solid bases, including most
builtins 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 baes,
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::

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 they are not subclasses of each other.
class C4(Solid1, Solid2): # error: no single solid base
pass

.. _async:

Coroutines
Expand Down
2 changes: 2 additions & 0 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2779,6 +2779,8 @@ Resolving MRO entries
Core support for typing module and generic types.


.. _metaclass-determination:

Determining the appropriate metaclass
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. index::
Expand Down
3 changes: 3 additions & 0 deletions Doc/tutorial/classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
Loading