8000 gh-116241: Add support of multiple inheritance with typing.NamedTuple by serhiy-storchaka · Pull Request #31781 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-116241: Add support of multiple inheritance with typing.NamedTuple #31781

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
bpo-43923: Add support of generic typing.NamedTuple
  • Loading branch information
serhiy-storchaka committed Apr 28, 2022
commit 939d5f03c1715d0481dfbfd8b8ddd30a4775643d
9 changes: 9 additions & 0 deletions Doc/library/typing.rst
8000
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 inheritance with :class:`Generic`.

.. 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
------

* :class:`~typing.NamedTuple` subclasses can be generic.
(Contributed by Serhiy Storchaka in :issue:`43923`.)


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

Expand Down
31 changes: 31 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5278,6 +5278,37 @@ class A:
with self.assertRaises(TypeError):
class X(NamedTuple, A):
x: int
with self.assertRaises(TypeError):
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
12 changes: 8 additions & 4 deletions Lib/typing.py
A4D5
Original file line number Diff line number Diff line change
Expand Up @@ -2763,7 +2763,12 @@ 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
for base in bases:
if base is not _NamedTuple and base is not Generic:
raise TypeError('can only inherit from a NamedTuple type '
'and Generic')
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 +2782,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 +2826,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,2 @@
Add support of multiple inheritance of :class:`typing.NamedTuple` with
:class:`typing.Generic`.
0