Skip to content
Merged
Changes from all commits
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
27 changes: 17 additions & 10 deletions peps/pep-0800.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ PEP: 800
Title: Disjoint bases in the type system
Author: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Discussions-To: https://discuss.python.org/t/99910/
Status: Draft
Status: Accepted
Type: Standards Track
Topic: Typing
Created: 21-Jul-2025
Python-Version: 3.15
Post-History: `18-Jul-2025 <https://discuss.python.org/t/solid-bases-for-detecting-incompatible-base-classes/99280>`__,
`23-Jul-2025 <https://discuss.python.org/t/99910>`__,
Resolution: `15-Apr-2026 <https://discuss.python.org/t/pep-800-solid-bases-in-the-type-system/99910/41>`__


Abstract
Expand All @@ -19,6 +20,10 @@ However, the information necessary to determine this is not currently part of th
decorator, ``@typing.disjoint_base``, that indicates that a class is a "disjoint base". Two classes that have distinct, unrelated
disjoint bases cannot have a common child class.

This decorator is not expected to be used directly by most users. It is primarily intended for use in stub files for
standard library and extension-module classes, where it helps type checkers reflect runtime restrictions
consistently.

Motivation
==========

Expand Down Expand Up @@ -63,11 +68,11 @@ incorrect in general, as discussed in more detail :ref:`below <pep-800-mypy-inco
The experimental ``ty`` type checker uses a third approach that aligns more closely with the :ref:`runtime behavior of Python <pep-800-solid-bases-cpython>`:
it recognizes certain classes as "solid bases" that restrict multiple inheritance. Broadly speaking, every class must
inherit from at most one unique solid base, and if there is no unique solid base, the class cannot exist; we'll provide a more
precise definition below. However, ty's approach relies on hardcoded knowledge of particular built-in types. The term "solid base" derives from the
precise definition below. However, ty's current approach relies on hardcoded knowledge of particular built-in types. The term "solid base" derives from the
CPython implementation; this PEP uses the newly proposed term "disjoint base" instead.

This PEP proposes an extension to the type system that makes it possible to express when multiple inheritance is not
allowed at runtime: an ``@disjoint_base`` decorator that marks a classes as a *disjoint base*.
allowed at runtime: an ``@disjoint_base`` decorator that marks a class as a *disjoint base*.
This gives type checkers a more precise understanding of reachability, and helps in several concrete areas.

Invalid class definitions
Expand Down Expand Up @@ -256,7 +261,7 @@ Similarly, the concept of a "disjoint base" is not meaningful on ``TypedDict`` d
Although they receive some special treatment in the type system, ``NamedTuple`` definitions create real nominal classes that can
have child classes, so it makes sense to allow ``@disjoint_base`` on them and treat them like regular classes for the purposes
of the disjoint base mechanism. All ``NamedTuple`` classes have ``tuple``, a disjoint base, in their MRO, so they
cannot multiple inherit from other disjoint bases.
cannot use multiple inheritance with other disjoint bases.

Specification
=============
Expand Down Expand Up @@ -363,8 +368,9 @@ None known.
How to Teach This
=================

Most users will not have to directly use or understand the ``@disjoint_base`` decorator, as the expectation is that will be
primarily used in library stubs for low-level libraries. Teachers of Python can introduce
Most users will not have to directly use or understand the ``@disjoint_base`` decorator, as the expectation is that it will be
primarily used in library stubs for low-level libraries. It should not be taught as a decorator that users should routinely
add to classes. Teachers of Python can introduce
the concept of "disjoint bases" to explain why multiple inheritance is not allowed in certain cases. Teachers of
Python typing can introduce the decorator when teaching type narrowing constructs like ``isinstance()`` to
explain to users why type checkers treat certain branches as unreachable.
Expand All @@ -377,7 +383,8 @@ The runtime implementation of the ``@disjoint_base`` decorator is available in
Mypy and its stubtest tool support the decorator as of version 1.18.1; this was implemented in
`python/mypy#19678 <https://github.com/python/mypy/pull/19678>`__.
Support was added to the ty type checker in
`astral-sh/ruff#20084 <https://github.com/astral-sh/ruff/pull/20084>`__.
`astral-sh/ruff#20084 <https://github.com/astral-sh/ruff/pull/20084>`__
and to pycroscope in `JelleZijlstra/pycroscope#431 <https://github.com/JelleZijlstra/pycroscope/pull/431>`__.

Appendix
========
Expand Down Expand Up @@ -465,9 +472,9 @@ Nevertheless, it accepts the following class definition without error::
def __rmul__(self, other: object) -> Never:
raise TypeError
def __ge__(self, other: int | str) -> bool:
return int(self) > other if isinstance(other, int) else str(self) > other
def __gt__(self, other: int | str) -> bool:
return int(self) >= other if isinstance(other, int) else str(self) >= other
def __gt__(self, other: int | str) -> bool:
return int(self) > other if isinstance(other, int) else str(self) > other
def __lt__(self, other: int | str) -> bool:
return int(self) < other if isinstance(other, int) else str(self) < other
def __le__(self, other: int | str) -> bool:
Expand Down Expand Up @@ -519,7 +526,7 @@ can also reject classes that have more practically useful implementations::
pass

Mypy's rule works reasonably well in practice for deducing whether an intersection of two
classes is inhabited. Most builtin classes that are disjoint bases happen to implement common dunder
classes is inhabited. Most built-in classes that are disjoint bases happen to implement common dunder
methods such as ``__add__`` and ``__iter__`` in incompatible ways, so mypy will consider them
incompatible. There are some exceptions: mypy allows ``class C(BaseException, int): ...``,
though both of these classes are disjoint bases and the class definition is rejected at runtime.
Expand Down
Loading