Skip to content

gh-129343: Add _repr method to named tuples #129353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
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 @@ -985,6 +986,21 @@ field names, the method and attribute names start with an underscore.
Raise :exc:`TypeError` instead of :exc:`ValueError` for invalid
keyword arguments.

.. 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

.. attribute:: somenamedtuple._fields

Tuple of strings listing the field names. Useful for introspection
Expand Down
11 changes: 11 additions & 0 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2341,6 +2341,17 @@ types.
def __repr__(self) -> str:
return f'<Employee {self.name}, id={self.id}>'

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
11 changes: 9 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,6 +509,7 @@ def __getnewargs__(self):
'_make': _make,
'__replace__': _replace,
'_replace': _replace,
'_repr': _repr,
'__repr__': __repr__,
'_asdict': _asdict,
'__getnewargs__': __getnewargs__,
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
18 changes: 18 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8135,6 +8135,24 @@ class Group(NamedTuple):
self.assertIs(type(a), Group)
self.assertEqual(a, (1, [2]))

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
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