Skip to content

bswck/make-typed-namedtuples-final

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 

Repository files navigation

Make typed namedtuples final

Related: python/typing#427

So, all classes like Bar from

from typing import NamedTuple

class Foo(NamedTuple):
    pass

class Bar(Foo):
    pass

should be illegal.

Why?

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.

Expected outcome

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 @final implicitly

Technical reasons

The problematic super()

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()

Multiple inheritance is not supported

As a Python user, for

from typing import NamedTuple

class Point2D(NamedTuple):
    x: int
    y: int

class Point3D(Point2D):
    z: int

I'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) -> None
from 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) -> None

Now, for

from typing import NamedTuple

class Foo(NamedTuple):
    x: int

class Bar(Foo):
    y: int

What'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:

  1. it's inherently unsupported to reuse typing.NamedTuple logic 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
    
  2. the metaclass of any direct typing.NamedTuple subclass is type, not typing.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 Foo is a namedtuple (and its metaclass is type as well).

Following up

Ask core devs?

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

Why do you care?

https://justforfunnoreally.dev/

Footnotes

  1. As of 9 Apr 2025, Pydantic was mentioned in 8 final/active/draft PEPs (specifically: PEP 746, active PEP 729, accepted PEP 649, final PEP 681, PEP 749, PEP 747, PEP 783, PEP 727) and attrs was mentioned in 5 (specifically: PEP 681, PEP 747, PEP 767, PEP 649, final PEP 557).

About

Invalidate typing.NamedTuple subclasses' subclasses.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published