Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
20 changes: 18 additions & 2 deletions Doc/library/collections.rst
Original file line number Diff line number Diff line change
Expand Up @@ -849,8 +849,9 @@ they add the ability to access fields by name instead of position index.
Returns a new tuple subclass named *typename*. The new subclass is used to
create tuple-like objects that have fields accessible by attribute lookup as
well as being indexable and iterable. Instances of the subclass also have a
helpful docstring (with typename and field_names) and a helpful :meth:`__repr__`
method which lists the tuple contents in a ``name=value`` format.
helpful docstring (with typename and field_names) and a helpful :meth:`~somenamedtuple._repr`
method, backing the default :meth:`~object.__repr__`, which lists the tuple
contents in a ``name=value`` format.

The *field_names* are a sequence of strings such as ``['x', 'y']``.
Alternatively, *field_names* can be a single string with each fieldname
Expand Down Expand Up @@ -967,6 +968,21 @@ field names, the method and attribute names start with an underscore.
remediation is to cast the result to the desired type:
``OrderedDict(nt._asdict())``.

.. method:: somenamedtuple._repr()

Return a representation of the named tuple contents in a ``name=value`` format.
The default ``__repr__`` implementation uses it to produce the representation.

.. doctest::

>>> p = Point(x=11, y=22)
>>> p._repr()
'Point(x=11, y=22)'
>>> p
Point(x=11, y=22)

.. versionadded:: 3.14

.. method:: somenamedtuple._replace(**kwargs)

Return a new instance of the named tuple replacing specified fields with new
Expand Down
14 changes: 14 additions & 0 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2341,6 +2341,20 @@ 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`.

To allow extending named tuple's default ``__repr__``, it can be as well accessed with ``self._repr``,
as ``super().__repr__`` in a ``NamedTuple`` subclass resolves to ``tuple.__repr__``::

class Import(NamedTuple):
target: str

def __repr__(self) -> str:
# super().__repr__() -> ('target',)
# self._repr() -> Import(target='target')
return f'<Token {self._repr()}>' # <Token Import(target='target')>

``NamedTuple`` subclasses can be generic::

class Group[T](NamedTuple):
Expand Down
15 changes: 13 additions & 2 deletions 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 @@ -469,10 +469,15 @@ def _replace(self, /, **kwds):
_replace.__doc__ = (f'Return a new {typename} object replacing specified '
'fields with new values')

def __repr__(self):
def _repr(self):
'Return a nicely formatted representation string'
return self.__class__.__name__ + repr_fmt % self

def __repr__(self):
return self._repr()

__repr__.__doc__ = _repr.__doc__

def _asdict(self):
'Return a new dict which maps field names to their values.'
return _dict(_zip(self._fields, self))
Expand All @@ -486,6 +491,7 @@ def __getnewargs__(self):
__new__,
_make.__func__,
_replace,
_repr,
__repr__,
_asdict,
__getnewargs__,
Expand All @@ -503,11 +509,16 @@ def __getnewargs__(self):
'_make': _make,
'__replace__': _replace,
'_replace': _replace,
'_repr': _repr,
'__repr__': __repr__,
'_asdict': _asdict,
'__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
2 changes: 2 additions & 0 deletions Lib/test/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,10 +648,12 @@ def test_name_conflicts(self):
def test_repr(self):
A = namedtuple('A', 'x')
self.assertEqual(repr(A(1)), 'A(x=1)')
self.assertEqual(A(2)._repr(), 'A(x=2)')
# repr should show the name of the subclass
class B(A):
pass
self.assertEqual(repr(B(1)), 'B(x=1)')
self.assertEqual(B(2)._repr(), 'B(x=2)')

def test_keyword_only_arguments(self):
# See issue 25628
Expand Down
37 changes: 37 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8135,6 +8135,43 @@ class Group(NamedTuple):
self.assertIs(type(a), Group)
self.assertEqual(a, (1, [2]))

def test_classcell_access(self):
# See #85795: __class__ not set defining 'X' as <class '__main__.X'>
class AspiringTriager(NamedTuple):
name: str = "Bartosz"

@property
def tablename(self):
return __class__.__name__.lower() + "s"

def count(self, item):
if item == "Bartosz":
return super().count(item)
return -1

aspiring_triager = AspiringTriager()
self.assertEqual(aspiring_triager.tablename, "aspiringtriagers")
self.assertEqual(aspiring_triager.count("Bartosz"), 1)
self.assertEqual(aspiring_triager.count("Peter"), -1) # already a triager!

def test_overridden_repr(self):
class CustomRepresentation(NamedTuple):
namedtuple_style: bool

def __repr__(self):
if self.namedtuple_style:
return f"<namedtuple style {self._repr()}>"
return f"<tuple style {super().__repr__()}>"

namedtuple_style_repr = CustomRepresentation(namedtuple_style=True)
self.assertEqual(
repr(namedtuple_style_repr),
"<namedtuple style CustomRepresentation(namedtuple_style=True)>"
)

tuple_style_repr = CustomRepresentation(namedtuple_style=False)
self.assertEqual(repr(tuple_style_repr), "<tuple style (False,)>")

def test_namedtuple_keyword_usage(self):
with self.assertWarnsRegex(
DeprecationWarning,
Expand Down
10 changes: 5 additions & 5 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 All @@ -2953,7 +2953,7 @@ def annotate(format):
# attributes prohibited to set in NamedTuple class syntax
_prohibited = frozenset({'__new__', '__init__', '__slots__', '__getnewargs__',
'_fields', '_field_defaults',
'_make', '_replace', '_asdict', '_source'})
'_make', '_replace', '_repr', '_asdict', '_source'})

_special = frozenset({'__module__', '__name__', '__annotations__', '__annotate__'})

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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Added :meth:`~collections.somenamedtuple._repr` method to named tuples for reuse in
custom :meth:`object.__repr__` implementations in
:class:`~typing.NamedTuple` subclasses. Contributed by Bartosz Sławecki.
Loading