Skip to content

Instantly share code, notes, and snippets.

@johnslavik
Last active April 10, 2026 19:07
Show Gist options
  • Select an option

  • Save johnslavik/4d0e22df6cb61868f74269fe7d401f5b to your computer and use it in GitHub Desktop.

Select an option

Save johnslavik/4d0e22df6cb61868f74269fe7d401f5b to your computer and use it in GitHub Desktop.

Context: python/cpython#31781

Snippets below are unsupported and currently raise an error:

>>> from typing import NamedTuple
...
... class Foo(NamedTuple):
...     x: int
...
... class Bar(NamedTuple, Foo):
...     y: int
...
Traceback (most recent call last):
  File "<python-input-0>", line 6, in <module>
    class Bar(NamedTuple, Foo):
        y: int
  File "/Users/bartosz.slawecki/Python/cpython/Lib/typing.py", line 3021, in __new__
    raise TypeError(
        'can only inherit from a NamedTuple type and Generic')
TypeError: can only inherit from a NamedTuple type and Generic
>>> from typing import NamedTuple
...
... class Foo(NamedTuple):
...     x: int
...
... class Bar(Foo, NamedTuple):
...     y: int
...
Traceback (most recent call last):
  File "<python-input-1>", line 6, in <module>
    class Bar(Foo, NamedTuple):
        y: int
  File "/Users/bartosz.slawecki/Python/cpython/Lib/typing.py", line 3021, in __new__
    raise TypeError(
        'can only inherit from a NamedTuple type and Generic')
TypeError: can only inherit from a NamedTuple type and Generic
>>>

with the change adding multiple inheritance support, the first snippet raises a less clear exception and the second silently passes:

>>> from typing import NamedTuple
...
... class Foo(NamedTuple):
...     x: int
...
... class Bar(NamedTuple, Foo):
...     y: int
...
Traceback (most recent call last):
  File "<python-input-0>", line 6, in <module>
    class Bar(NamedTuple, Foo):
        y: int
  File "/Users/bartosz.slawecki/Python/cpython/Lib/typing.py", line 3056, in __new__
    nm_tpl.__bases__ = bases
    ^^^^^^^^^^^^^^^^
TypeError: Cannot create a consistent method resolution order (MRO) for bases tuple, Foo
>>> from typing import NamedTuple
...
... class Foo(NamedTuple):
...     x: int
...
... class Bar(Foo, NamedTuple):
...     y: int
...
>>>

The latter is passing, so there is a chance of people starting to rely on it. It isn't easy to find a use case for utilizing this new possibility, but it has a number of issues:

  • super() is illegal in any typing.NamedTuples,

  • It creates a namedtuple from the annotations of the child class only:

    >>> class Foo(NamedTuple):
    ...     a: int
    ...     b: int
    ...
    ... class Bar(Foo, NamedTuple):
    ...     x: int
    ...     y: int
    ...
    >>> Bar._fields
    ('x', 'y')

    However, the fields of the parent typing.NamedTuple are populated with fields of the child despite name mismatch. It is a reasonable expectation but a separate feature request to expect fields to be extended when a NamedTuple inherits a NamedTuple, but it is not supported and the actual implications of the second snippet are very uneasy to explain:

    >>> from typing import NamedTuple
    >>>
    >>> class BaseRecord(NamedTuple):
    ...     id: int
    ...     timestamp: float
    >>>
    >>> class LogEntry(BaseRecord, NamedTuple):
    ...     level: str
    ...     message: str
    >>>
    >>> entry = LogEntry(level="ERROR", message="disk full")
    >>> entry.id, entry.message
    ('ERROR', 'disk full')
    >>> entry.level, entry.timestamp
    ('ERROR', 'disk full')

    The fact that .id and .timestamp fields (that there is no control over) are set as an unintended side effect of populating .level and .message is a bug, even though it is unknown how severe. I think it is better to continue preventing this specific case from being possible to execute at runtime.

We could document that making a typing.NamedTuple inheriting typing.NamedTuple is undefined behavior or consider identifying such calls and make both raise an exception:

>>> from typing import NamedTuple
...
... class Foo(NamedTuple):
...     x: int
...
... class Bar(NamedTuple, Foo):
...     y: int
...
Traceback (most recent call last):
  ...
TypeError: a NamedTuple cannot inherit from a NamedTuple
>>> from typing import NamedTuple
...
... class Foo(NamedTuple):
...     x: int
...
... class Bar(Foo, NamedTuple):
...     y: int
...
Traceback (most recent call last):
  ...
TypeError: a NamedTuple cannot inherit from a NamedTuple

Right now, it could be implemented by rejecting any base whose .__orig_bases__ contain the NamedTuple function. Relying on .__orig_bases__ could be brittle, because that attribute can be removed, and the NamedTuple function is recognized by identity, but it might be a good enough heuristic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment