From 939d5f03c1715d0481dfbfd8b8ddd30a4775643d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 9 Mar 2022 12:12:08 +0200 Subject: [PATCH 1/3] bpo-43923: Add support of generic typing.NamedTuple --- Doc/library/typing.rst | 9 ++++++ Doc/whatsnew/3.11.rst | 7 +++++ Lib/test/test_typing.py | 31 +++++++++++++++++++ Lib/typing.py | 12 ++++--- ...2-04-28-18-45-58.gh-issue-88089.hu9kRk.rst | 2 ++ 5 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-04-28-18-45-58.gh-issue-88089.hu9kRk.rst diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 4635da7579ac75..7e97c9a1b22bbf 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -1606,6 +1606,12 @@ These are not used in annotations. They are building blocks for declaring types. def __repr__(self) -> str: return f'' + ``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)]) @@ -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, diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index b2b98747d31a14..6b51148433932d 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -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 ----------- diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index a904b7a790c04d..da763ceed4efa2 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -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) diff --git a/Lib/typing.py b/Lib/typing.py index f4d4fa4d6713c5..8e2e8aa2cbe58b 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -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: @@ -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: @@ -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 diff --git a/Misc/NEWS.d/next/Library/2022-04-28-18-45-58.gh-issue-88089.hu9kRk.rst b/Misc/NEWS.d/next/Library/2022-04-28-18-45-58.gh-issue-88089.hu9kRk.rst new file mode 100644 index 00000000000000..c531e5426aab65 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-04-28-18-45-58.gh-issue-88089.hu9kRk.rst @@ -0,0 +1,2 @@ +Add support of multiple inheritance of :class:`typing.NamedTuple` with +:class:`typing.Generic`. From 64f0c5f6ef80db55d2005ddacd100adad41eecae Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 9 Mar 2022 12:12:08 +0200 Subject: [PATCH 2/3] bpo-43923: Add support of multiple inheritance with typing.NamedTuple --- Doc/library/typing.rst | 2 +- Doc/whatsnew/3.11.rst | 2 +- Lib/test/test_typing.py | 36 ++++++++++++++++--- Lib/typing.py | 4 --- ...2-04-28-18-45-58.gh-issue-88089.hu9kRk.rst | 3 +- 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 7e97c9a1b22bbf..72f2ca829b73b7 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -1631,7 +1631,7 @@ These are not used in annotations. They are building blocks for declaring types. standard ``__annotations__`` attribute which has the same information. .. versionchanged:: 3.11 - Added support of multiple inheritance with :class:`Generic`. + Added support of multiple inheritence. .. class:: NewType(name, tp) diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 6b51148433932d..94b58ba6d59368 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -644,7 +644,7 @@ time typing ------ -* :class:`~typing.NamedTuple` subclasses can be generic. +* Added support of multiinheritance with :class:`~typing.NamedTuple`. (Contributed by Serhiy Storchaka in :issue:`43923`.) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index da763ceed4efa2..0edc4f12662277 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -5274,10 +5274,38 @@ def _source(self): def test_multiple_inheritance(self): class A: - pass - with self.assertRaises(TypeError): - class X(NamedTuple, A): - x: int + @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): + 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, tuple): x: int diff --git a/Lib/typing.py b/Lib/typing.py index 8e2e8aa2cbe58b..abfc2e09856604 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2764,10 +2764,6 @@ class NamedTupleMeta(type): def __new__(cls, typename, bases, ns): 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 = [] diff --git a/Misc/NEWS.d/next/Library/2022-04-28-18-45-58.gh-issue-88089.hu9kRk.rst b/Misc/NEWS.d/next/Library/2022-04-28-18-45-58.gh-issue-88089.hu9kRk.rst index c531e5426aab65..af986fce982ac3 100644 --- a/Misc/NEWS.d/next/Library/2022-04-28-18-45-58.gh-issue-88089.hu9kRk.rst +++ b/Misc/NEWS.d/next/Library/2022-04-28-18-45-58.gh-issue-88089.hu9kRk.rst @@ -1,2 +1 @@ -Add support of multiple inheritance of :class:`typing.NamedTuple` with -:class:`typing.Generic`. +Add support of multiple inheritance with :class:`typing.NamedTuple`. From 9d48f87a3d256bb6cfc9c7b386d06bb2815308ca Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 9 Oct 2024 19:27:33 +0300 Subject: [PATCH 3/3] Address review comments. --- Doc/library/typing.rst | 4 ++-- Doc/whatsnew/3.13.rst | 3 --- Doc/whatsnew/3.14.rst | 5 +++++ .../Library/2022-04-28-18-45-58.gh-issue-116241.hu9kRk.rst | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 3facbf918254ed..4ab5a3c2200084 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2389,8 +2389,8 @@ types. disallowed in Python 3.15. To create a NamedTuple class with 0 fields, use ``class NT(NamedTuple): pass`` or ``NT = NamedTuple("NT", [])``. - .. versionchanged:: 3.13 - Added support of multiple inheritence. + .. versionchanged:: 3.14 + Added support for arbitrary multiple inheritance. .. class:: NewType(name, tp) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 3f87423228ccd3..a2897097aaba57 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1397,9 +1397,6 @@ typing (Contributed by Mehdi Drissi in :gh:`89547`.) -* Add support of multiple inheritance with :class:`~typing.NamedTuple`. - (Contributed by Serhiy Storchaka in :gh:`116241`.) - unicodedata ----------- diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index c62a3ca5872eef..75ec49d9f46c5d 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -574,6 +574,11 @@ typing * Remove :class:`!typing.ByteString`. It had previously raised a :exc:`DeprecationWarning` since Python 3.12. +* Add support of multiple inheritance with :class:`~typing.NamedTuple`. + Previously, multiple inheritance was only supported if there was exactly + one other base and the base was :class:`typing.Generic`. + (Contributed by Serhiy Storchaka in :gh:`116241`.) + urllib ------ diff --git a/Misc/NEWS.d/next/Library/2022-04-28-18-45-58.gh-issue-116241.hu9kRk.rst b/Misc/NEWS.d/next/Library/2022-04-28-18-45-58.gh-issue-116241.hu9kRk.rst index af986fce982ac3..0cbfb8ad12f6e3 100644 --- a/Misc/NEWS.d/next/Library/2022-04-28-18-45-58.gh-issue-116241.hu9kRk.rst +++ b/Misc/NEWS.d/next/Library/2022-04-28-18-45-58.gh-issue-116241.hu9kRk.rst @@ -1 +1 @@ -Add support of multiple inheritance with :class:`typing.NamedTuple`. +Add support for arbitrary multiple inheritance with :class:`typing.NamedTuple`.