This proposal aims to make typing.NamedTuple classes final—disallowing subclassing—in order to eliminate long-standing inconsistencies, surprising runtime behavior, and broken inheritance semantics.
Consider the following example:
from typing import NamedTuple
class Point2D(NamedTuple):
x: int
y: int
class Point3D(Point2D):
z: int
A reasonable expectation is that the constructor signature for Point3D would be:
(x: int, y: int, z: int)
However, the actual constructor signature is:
(x: int, y: int)
The z annotation in the subclass does not add a new field. This is because subclasses of typing.NamedTuple classes are not reprocessed to include additional annotations—unlike similar annotation-based data structures in dataclasses, attrs, or Pydantic.
Comparison with other tools
Other popular Python tools do support extending fields via subclass annotations:
Examples
Pydantic
import inspect
from pydantic import BaseModel
class Point2D(BaseModel):
x: int
y: int
class Point3D(Point2D): # Semantics stem from inheritance
z: int
print(inspect.signature(Point3D))
# (*, x: int, y: int, z: int) -> None
Dataclasses (stdlib)
import inspect
from dataclasses import dataclass
@dataclass
class Point2D:
x: int
y: int
@dataclass # The decorator reapplies semantics respecting inheritance
class Point3D(Point2D):
z: int
print(inspect.signature(Point3D))
# (x: int, y: int, z: int) -> None
attrs
import inspect
from attrs import define
@define
class Point2D:
x: int
y: int
@define # The decorator reapplies semantics respecting inheritance
class Point3D(Point2D):
z: int
print(inspect.signature(Point3D))
# (x: int, y: int, z: int) -> None
In contrast, typing.NamedTuple does not support this pattern—and is unlikely to change due to deep technical constraints and lack of compelling use cases that are not better served by other tools.
The problem
typing.NamedTuple breaks the core inheritance mechanisms of Python.
Subclassing typing.NamedTuple classes creates the illusion of behavior similar to that in other tools, but:
- The constructor is not updated to include new fields.
super()does not work in methods oftyping.NamedTupleclasses.
Example: super() behavior
from typing import NamedTuple
class Foo(NamedTuple):
pass
class Bar(Foo):
def biz(self) -> None:
super() # Works, delegates to tuple or object
But:
from typing import NamedTuple
class Foo(NamedTuple):
def bar(self) -> None:
super() # Raises at runtime
In Python ≤ 3.13:
RuntimeError: __class__ not set defining 'Foo' as <class '__main__.Foo'>.
Was __classcell__ propagated to type.__new__?
In Python ≥ 3.14 (after python/cpython#130082):
TypeError: uses of super() and __class__ are unsupported in methods of NamedTuple subclasses
typing.NamedTuple is implemented as a function that dynamically creates and returns a new class (via collections.namedtuple). This does not propagate the __classcell__, so the __class__ closure variable is never bound — causing zero-argument super() to fail.
Reapplying typing.NamedTuple logic is impossible
Attempting to reapply NamedTuple processing to a subclass fails:
from typing import NamedTuple, NamedTupleMeta
class Foo(NamedTuple):
x: int
class Bar(Foo, metaclass=NamedTupleMeta):
y: int
Traceback (assertions disabled):
Traceback (most recent call last):
File "make-typed-namedtuples-final.py", line 6, in <module>
class Bar(Foo, metaclass=NamedTupleMeta):
y: int
File ".../Lib/typing.py", line 2889, in __new__
raise TypeError(
'can only inherit from a NamedTuple type and Generic')
TypeError: can only inherit from a NamedTuple type and Generic
The metaclass of typing.NamedTuple class becomes type, not typing.NamedTupleMeta. Therefore typing.NamedTupleMeta only works for typing.NamedTuple classes, but not their subclasses.
Why this matters
- The current behavior is surprising and inconsistent with other Python data modeling tools, violating the principle of least astonishment.
- It has been a source of confusion and bug reports for some years.
- Preventing subclassing would make the intended use of
NamedTupleexplicit:
lightweight, immutable, iterable, slot-based, final data structures.
Proposed change
at runtime
- Python 3.15: Subclassing a
NamedTupleemits aDeprecationWarning. - Python 3.20: Subclassing a
NamedTupleraises immediately:
TypeError: typing.NamedTuple classes cannot be subclassed
in the typing specification
- Immediately after acceptance, the typing spec requires type checkers to treat all
NamedTupleclasses as if decorated with@final.
Prior art and discussion
- python/typing#427 — constructor behavior first reported by @guido (2017).
- python/mypy#3521 — request for subtyping support was discussed and not proposed.
- Functional API for
NamedTuplealready supports field composition without subclassing.
Reference implementation
- 3.15 deprecation: bswck/cpython,
final-namedtuple-proposal-deprecationbranch (see essential commit) - 3.20 error: bswck/cpython,
final-namedtuple-proposalbranch (essential commit)
What do you think about this idea? Would it be useful for Python programmers? Would it help them align with the intended design of typing.NamedTuple?