mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
bpo-43923: Add support for generic typing.NamedTuple (#92027)
This commit is contained in:
parent
81fb3548be
commit
b04e02c57f
5 changed files with 66 additions and 4 deletions
|
@ -1615,6 +1615,12 @@ These are not used in annotations. They are building blocks for declaring types.
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'<Employee {self.name}, id={self.id}>'
|
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::
|
Backward-compatible usage::
|
||||||
|
|
||||||
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
|
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
|
Removed the ``_field_types`` attribute in favor of the more
|
||||||
standard ``__annotations__`` attribute which has the same information.
|
standard ``__annotations__`` attribute which has the same information.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.11
|
||||||
|
Added support for generic namedtuples.
|
||||||
|
|
||||||
.. class:: NewType(name, tp)
|
.. class:: NewType(name, tp)
|
||||||
|
|
||||||
A helper class to indicate a distinct type to a typechecker,
|
A helper class to indicate a distinct type to a typechecker,
|
||||||
|
|
|
@ -715,6 +715,10 @@ For major changes, see :ref:`new-feat-related-type-hints-311`.
|
||||||
to clear all registered overloads of a function.
|
to clear all registered overloads of a function.
|
||||||
(Contributed by Jelle Zijlstra in :gh:`89263`.)
|
(Contributed by Jelle Zijlstra in :gh:`89263`.)
|
||||||
|
|
||||||
|
* :class:`~typing.NamedTuple` subclasses can be generic.
|
||||||
|
(Contributed by Serhiy Storchaka in :issue:`43923`.)
|
||||||
|
|
||||||
|
|
||||||
unicodedata
|
unicodedata
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
|
@ -5678,6 +5678,45 @@ class NamedTupleTests(BaseTestCase):
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
class X(NamedTuple, A):
|
class X(NamedTuple, A):
|
||||||
x: int
|
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):
|
def test_namedtuple_keyword_usage(self):
|
||||||
LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int)
|
LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int)
|
||||||
|
|
|
@ -2764,7 +2764,12 @@ _special = frozenset({'__module__', '__name__', '__annotations__'})
|
||||||
class NamedTupleMeta(type):
|
class NamedTupleMeta(type):
|
||||||
|
|
||||||
def __new__(cls, typename, bases, ns):
|
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__', {})
|
types = ns.get('__annotations__', {})
|
||||||
default_names = []
|
default_names = []
|
||||||
for field_name in types:
|
for field_name in types:
|
||||||
|
@ -2778,12 +2783,18 @@ class NamedTupleMeta(type):
|
||||||
nm_tpl = _make_nmtuple(typename, types.items(),
|
nm_tpl = _make_nmtuple(typename, types.items(),
|
||||||
defaults=[ns[n] for n in default_names],
|
defaults=[ns[n] for n in default_names],
|
||||||
module=ns['__module__'])
|
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
|
# update from user namespace without overriding special namedtuple attributes
|
||||||
for key in ns:
|
for key in ns:
|
||||||
if key in _prohibited:
|
if key in _prohibited:
|
||||||
raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
|
raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
|
||||||
elif key not in _special and key not in nm_tpl._fields:
|
elif key not in _special and key not in nm_tpl._fields:
|
||||||
setattr(nm_tpl, key, ns[key])
|
setattr(nm_tpl, key, ns[key])
|
||||||
|
if Generic in bases:
|
||||||
|
nm_tpl.__init_subclass__()
|
||||||
return nm_tpl
|
return nm_tpl
|
||||||
|
|
||||||
|
|
||||||
|
@ -2821,9 +2832,7 @@ def NamedTuple(typename, fields=None, /, **kwargs):
|
||||||
_NamedTuple = type.__new__(NamedTupleMeta, 'NamedTuple', (), {})
|
_NamedTuple = type.__new__(NamedTupleMeta, 'NamedTuple', (), {})
|
||||||
|
|
||||||
def _namedtuple_mro_entries(bases):
|
def _namedtuple_mro_entries(bases):
|
||||||
if len(bases) > 1:
|
assert NamedTuple in bases
|
||||||
raise TypeError("Multiple inheritance with NamedTuple is not supported")
|
|
||||||
assert bases[0] is NamedTuple
|
|
||||||
return (_NamedTuple,)
|
return (_NamedTuple,)
|
||||||
|
|
||||||
NamedTuple.__mro_entries__ = _namedtuple_mro_entries
|
NamedTuple.__mro_entries__ = _namedtuple_mro_entries
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Add support for generic :class:`typing.NamedTuple`.
|
Loading…
Add table
Add a link
Reference in a new issue