Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 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
3 changes: 3 additions & 0 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2341,6 +2341,9 @@ types.
def __repr__(self) -> str:
return f'<Employee {self.name}, id={self.id}>'

Calls to :func:`super` are supported inside user-defined methods of ``NamedTuple`` subclasses
to reuse functionality from built-in classes :class:`tuple` and :class:`object`.

``NamedTuple`` subclasses can be generic::

class Group[T](NamedTuple):
Expand Down
6 changes: 5 additions & 1 deletion Lib/collections/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ def __ror__(self, other):
except ImportError:
_tuplegetter = lambda index, doc: property(_itemgetter(index), doc=doc)

def namedtuple(typename, field_names, *, rename=False, defaults=None, module=None):
def namedtuple(typename, field_names, *, rename=False, defaults=None, module=None, _classcell=None):
"""Returns a new subclass of tuple with named fields.

>>> Point = namedtuple('Point', ['x', 'y'])
Expand Down Expand Up @@ -508,6 +508,10 @@ def __getnewargs__(self):
'__getnewargs__': __getnewargs__,
'__match_args__': field_names,
}

if _classcell is not None:
class_namespace["__classcell__"] = _classcell

for index, name in enumerate(field_names):
doc = _sys.intern(f'Alias for field number {index}')
class_namespace[name] = _tuplegetter(index, doc)
Expand Down
32 changes: 32 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8135,6 +8135,38 @@ class Group(NamedTuple):
self.assertIs(type(a), Group)
self.assertEqual(a, (1, [2]))

def test_super_and_dunder_class_work(self):
# See #85795: __class__ not set defining 'X' as <class '__main__.X'>

class Pointer(NamedTuple):
address: int
target_type = "int"

@property
def typename(self):
return __class__.target_type

def count(self, item):
if item == 0:
return -1
return super().count(self.address)

ptr = Pointer(0xdeadbeef)
self.assertEqual(ptr.typename, "int")
self.assertEqual(ptr.count(0), -1)
self.assertEqual(ptr.count(0xdeadbeef), 1)

@cpython_only
def test_classcell_not_leaked(self):
# __classcell__ should never leak into end classes

class Spam(NamedTuple):
lambda: super()
lambda: __class__

with self.assertRaises(AttributeError):
Spam.__classcell__

def test_namedtuple_keyword_usage(self):
with self.assertWarnsRegex(
DeprecationWarning,
Expand Down
8 changes: 4 additions & 4 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2929,9 +2929,9 @@ def __round__(self, ndigits: int = 0) -> T:
pass


def _make_nmtuple(name, fields, annotate_func, module, defaults = ()):
nm_tpl = collections.namedtuple(name, fields,
defaults=defaults, module=module)
def _make_nmtuple(name, fields, annotate_func, module, defaults = (), _classcell=None):
nm_tpl = collections.namedtuple(name, fields, defaults=defaults,
module=module, _classcell=_classcell)
nm_tpl.__annotate__ = nm_tpl.__new__.__annotate__ = annotate_func
return nm_tpl

Expand Down Expand Up @@ -3000,7 +3000,7 @@ def annotate(format):
f"{', '.join(default_names)}")
nm_tpl = _make_nmtuple(typename, field_names, annotate,
defaults=[ns[n] for n in default_names],
module=ns['__module__'])
module=ns['__module__'], _classcell=ns.pop("__classcell__", None))
nm_tpl.__bases__ = bases
if Generic in bases:
class_getitem = _generic_class_getitem
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added support for :func:`super` calls in user-defined
:class:`~typing.NamedTuple` methods. Contributed by Bartosz Sławecki.
Loading