summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/typing.rst9
-rw-r--r--Doc/whatsnew/3.11.rst4
-rw-r--r--Lib/test/test_typing.py39
-rw-r--r--Lib/typing.py17
-rw-r--r--Misc/NEWS.d/next/Library/2022-04-28-18-45-58.gh-issue-88089.hu9kRk.rst1
5 files changed, 66 insertions, 4 deletions
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index 868ea1b..05ac057 100644
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -1615,6 +1615,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)])
@@ -1633,6 +1639,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 for generic namedtuples.
+
.. 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 0a8ba1e..80ce462 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -715,6 +715,10 @@ For major changes, see :ref:`new-feat-related-type-hints-311`.
to clear all registered overloads of a function.
(Contributed by Jelle Zijlstra in :gh:`89263`.)
+* :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 cb7ca36..c074e7a 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -5678,6 +5678,45 @@ class NamedTupleTests(BaseTestCase):
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))
+
+ class Y(Generic[T], NamedTuple):
+ x: T
+ self.assertEqual(Y.__bases__, (Generic, tuple))
+ self.assertEqual(Y.__orig_bases__, (Generic[T], NamedTuple))
+ self.assertEqual(Y.__mro__, (Y, Generic, tuple, object))
+
+ for G in X, Y:
+ with self.subTest(type=G):
+ self.assertEqual(G.__parameters__, (T,))
+ A = G[int]
+ self.assertIs(A.__origin__, G)
+ self.assertEqual(A.__args__, (int,))
+ self.assertEqual(A.__parameters__, ())
+
+ a = A(3)
+ self.assertIs(type(a), G)
+ self.assertEqual(a.x, 3)
+
+ with self.assertRaises(TypeError):
+ G[int, str]
def test_namedtuple_keyword_usage(self):
LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int)
diff --git a/Lib/typing.py b/Lib/typing.py
index 35deb32..29a3f43 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -2764,7 +2764,12 @@ _special = frozenset({'__module__', '__name__', '__annotations__'})
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:
@@ -2778,12 +2783,18 @@ class NamedTupleMeta(type):
nm_tpl = _make_nmtuple(typename, types.items(),
defaults=[ns[n] for n in default_names],
module=ns['__module__'])
+ nm_tpl.__bases__ = bases
+ if Generic in bases:
+ class_getitem = Generic.__class_getitem__.__func__
+ nm_tpl.__class_getitem__ = classmethod(class_getitem)
# update from user namespace without overriding special namedtuple attributes
for key in ns:
if key in _prohibited:
raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
elif key not in _special and key not in nm_tpl._fields:
setattr(nm_tpl, key, ns[key])
+ if Generic in bases:
+ nm_tpl.__init_subclass__()
return nm_tpl
@@ -2821,9 +2832,7 @@ def NamedTuple(typename, fields=None, /, **kwargs):
_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 0000000..2665a47
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-04-28-18-45-58.gh-issue-88089.hu9kRk.rst
@@ -0,0 +1 @@
+Add support for generic :class:`typing.NamedTuple`.