Related: python/typing#427
So, all classes like Bar from
from typing import NamedTuple
class Foo(NamedTuple):
pass
class Bar(Foo):
passshould be illegal.
Supporting them was never explicitly intended nor documented.
While they look like they inherit structure, they don’t inherit behavior—leading to broken expectations around things like constructors and super().
There’s no clear, compelling use case that isn’t better served by dataclasses or other tools.
I expect this experiment to end up in
- the class statement with inheritance from a typed namedtuple to fail with a
TypeError("can't inherit from typed named tuples") - a clear requirement in the typing specification for type checkers to understand typed namedtuples as
@finalimplicitly
typing.NamedTuple subclasses' subclasses support super(), but bare typing.NamedTuple subclasses don't.
You can do
from typing import NamedTuple
class Foo(NamedTuple):
pass
class Bar(Foo):
def biz(self) -> None:
super()but you can't do
from typing import NamedTuple
class Foo(NamedTuple):
def bar(self) -> None:
super()As a Python user, for
from typing import NamedTuple
class Point2D(NamedTuple):
x: int
y: int
class Point3D(Point2D):
z: intI'd typically expect Point3D to extend Point2D with a new field z.
Right?
The same mechanism works in Pydantic, dataclasses, and attrs. These libraries become more and more present in PEPs1, so yes, general patterns from them are worth consideration.
Most relevant, because it has class-based API:
from pydantic import BaseModel
class Foo(BaseModel):
x: int
class Bar(Foo):
y: int
print(inspect.signature(Bar))
# (*, x: int, y: int) -> None(standard library)
@dataclass
class Foo:
x: int
@dataclass # if omitted, pure inheritance does nothing with the subclass
class Bar(Foo):
y: int
print(inspect.signature(Bar))
# (x: int, y: int) -> Nonefrom attrs import define
@define()
class Foo:
x: int
@define() # if omitted, pure inheritance does nothing with the subclass
class Bar(Foo):
y: int
print(inspect.signature(Bar))
# (x: int, y: int) -> NoneNow, for
from typing import NamedTuple
class Foo(NamedTuple):
x: int
class Bar(Foo):
y: intWhat's the expected constructor of Bar?
(x: int, y: int), (x: int) or (y: int)?
At runtime, it's (x: int):
import inspect
print(inspect.signature(Bar))That's because:
-
it's inherently unsupported to reuse
typing.NamedTuplelogic on such a subclass, because typed named tuples can't be mixed with arbitrary bases (python/cpython#116241):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 "/home/bswck/Python/cpython/t.py", line 6, in <module> class Bar(Foo, metaclass=NamedTupleMeta): y: int File "/home/bswck/Python/cpython/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 any direct
typing.NamedTuplesubclass istype, nottyping.NamedTupleMeta:from typing import NamedTuple, NamedTupleMeta class Foo(NamedTuple): x: int class Bar(Foo): y: int print(type(Bar)) # <class 'type'>
which is correct and expected, because
Foois a namedtuple (and its metaclass istypeas well).
They may have useful insights, especially those who contributed to python/cpython#72742.
Do you remember if it was intended to support subclasses of typing.NamedTuple "subclasses"?
- Jelle Zijlstra: I don't think it was
https://justforfunnoreally.dev/