Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
9 changes: 9 additions & 0 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1606,6 +1606,12 @@ These are not used in annotations. They are building blocks for declaring types.
def __repr__(self) -> str:
return f'<Employee {self.name}, id={self.id}>'

``NamedTuple`` subclasses can be generic::

class Group(NamedTuple, Generic[T]):
key: T
group: list[T]

Backward-compatible usage::

Employee = NamedTuple('Employee', [('name', str), ('id', int)])
Expand All @@ -1624,6 +1630,9 @@ These are not used in annotations. They are building blocks for declaring types.
Removed the ``_field_types`` attribute in favor of the more
standard ``__annotations__`` attribute which has the same information.

.. versionchanged:: 3.11
Added support of multiple inheritence.

.. class:: NewType(name, tp)

A helper class to indicate a distinct type to a typechecker,
Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,13 @@ time
(Contributed by Benjamin Szőke, Dong-hee Na, Eryk Sun and Victor Stinner in :issue:`21302` and :issue:`45429`.)


typing
------

* Added support of multiinheritance with :class:`~typing.NamedTuple`.
(Contributed by Serhiy Storchaka in :issue:`43923`.)


unicodedata
-----------

Expand Down
63 changes: 61 additions & 2 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5274,10 +5274,69 @@ def _source(self):

def test_multiple_inheritance(self):
class A:
pass
@property
def x(self):
return 4
@property
def y(self):
return 5
def __len__(self):
return 10

class X(NamedTuple, A):
x: int
self.assertEqual(X.__bases__, (tuple, A))
self.assertEqual(X.__orig_bases__, (NamedTuple, A))
self.assertEqual(X.__mro__, (X, tuple, A, object))

a = X(3)
self.assertEqual(a.x, 3)
self.assertEqual(a.y, 5)
self.assertEqual(len(a), 1)

class Y(A, NamedTuple):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a test to check the order of the members as well when doing unpacking? And maybe test class Z(X, Y, NamedTuple)? (I don't know if this leads to a MRO incompatibilty though)

x: int
self.assertEqual(Y.__bases__, (A, tuple))
self.assertEqual(Y.__orig_bases__, (A, NamedTuple))
self.assertEqual(Y.__mro__, (Y, A, tuple, object))

a = Y(3)
self.assertEqual(a.x, 3)
self.assertEqual(a.y, 5)
self.assertEqual(len(a), 10)

def test_multiple_inheritance_errors(self):
with self.assertRaises(TypeError):
class X(NamedTuple, A):
class X(NamedTuple, tuple):
x: int
with self.assertRaises(TypeError):
class X(NamedTuple, NamedTuple):
x: int
class A(NamedTuple):
x: int
with self.assertRaises(TypeError):
class X(NamedTuple, A):
y: str

def test_generic(self):
class X(NamedTuple, Generic[T]):
x: T
self.assertEqual(X.__bases__, (tuple, Generic))
self.assertEqual(X.__orig_bases__, (NamedTuple, Generic[T]))
self.assertEqual(X.__mro__, (X, tuple, Generic, object))

A = X[int]
self.assertEqual(A.__bases__, (tuple, Generic))
self.assertEqual(A.__orig_bases__, (NamedTuple, Generic[T]))
self.assertEqual(A.__mro__, (X, tuple, Generic, object))
self.assertIs(A.__origin__, X)
self.assertEqual(A.__args__, (int,))
self.assertEqual(A.__parameters__, ())

a = A(3)
self.assertIs(type(a), X)
self.assertEqual(a.x, 3)


def test_namedtuple_keyword_usage(self):
LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int)
Expand Down
8 changes: 4 additions & 4 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2763,7 +2763,8 @@ def _make_nmtuple(name, types, module, defaults = ()):
class NamedTupleMeta(type):

def __new__(cls, typename, bases, ns):
assert bases[0] is _NamedTuple
assert _NamedTuple in bases
bases = tuple(tuple if base is _NamedTuple else base for base in bases)
types = ns.get('__annotations__', {})
default_names = []
for field_name in types:
Expand All @@ -2777,6 +2778,7 @@ def __new__(cls, typename, bases, ns):
nm_tpl = _make_nmtuple(typename, types.items(),
defaults=[ns[n] for n in default_names],
module=ns['__module__'])
nm_tpl.__bases__ = bases
# update from user namespace without overriding special namedtuple attributes
for key in ns:
if key in _prohibited:
Expand Down Expand Up @@ -2820,9 +2822,7 @@ class Employee(NamedTuple):
_NamedTuple = type.__new__(NamedTupleMeta, 'NamedTuple', (), {})

def _namedtuple_mro_entries(bases):
if len(bases) > 1:
raise TypeError("Multiple inheritance with NamedTuple is not supported")
assert bases[0] is NamedTuple
assert NamedTuple in bases
return (_NamedTuple,)

NamedTuple.__mro_entries__ = _namedtuple_mro_entries
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support of multiple inheritance with :class:`typing.NamedTuple`.