Skip to content

Commit c4397f6

Browse files
committed
Add _repr method to named tuples
1 parent 7ec1742 commit c4397f6

File tree

6 files changed

+61
-4
lines changed

6 files changed

+61
-4
lines changed

Doc/library/collections.rst

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -849,8 +849,9 @@ they add the ability to access fields by name instead of position index.
849849
Returns a new tuple subclass named *typename*. The new subclass is used to
850850
create tuple-like objects that have fields accessible by attribute lookup as
851851
well as being indexable and iterable. Instances of the subclass also have a
852-
helpful docstring (with typename and field_names) and a helpful :meth:`__repr__`
853-
method which lists the tuple contents in a ``name=value`` format.
852+
helpful docstring (with typename and field_names) and a helpful :meth:`~somenamedtuple._repr`
853+
method, backing the default :meth:`~object.__repr__`, which lists the tuple
854+
contents in a ``name=value`` format.
854855

855856
The *field_names* are a sequence of strings such as ``['x', 'y']``.
856857
Alternatively, *field_names* can be a single string with each fieldname
@@ -985,6 +986,21 @@ field names, the method and attribute names start with an underscore.
985986
Raise :exc:`TypeError` instead of :exc:`ValueError` for invalid
986987
keyword arguments.
987988

989+
.. method:: somenamedtuple._repr()
990+
991+
Return a representation of the named tuple contents in a ``name=value`` format.
992+
The default ``__repr__`` implementation uses it to produce the representation.
993+
994+
.. doctest::
995+
996+
>>> p = Point(x=11, y=22)
997+
>>> p._repr()
998+
'Point(x=11, y=22)'
999+
>>> p
1000+
Point(x=11, y=22)
1001+
1002+
.. versionadded:: 3.14
1003+
9881004
.. attribute:: somenamedtuple._fields
9891005

9901006
Tuple of strings listing the field names. Useful for introspection

Doc/library/typing.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2341,6 +2341,17 @@ types.
23412341
def __repr__(self) -> str:
23422342
return f'<Employee {self.name}, id={self.id}>'
23432343

2344+
To allow extending named tuple's default ``__repr__``, it can be as well accessed with ``self._repr``,
2345+
as ``super().__repr__`` in a ``NamedTuple`` subclass resolves to ``tuple.__repr__``::
2346+
2347+
class Import(NamedTuple):
2348+
target: str
2349+
2350+
def __repr__(self) -> str:
2351+
# super().__repr__() -> ('target',)
2352+
# self._repr() -> Import(target='target')
2353+
return f'<Token {self._repr()}>' # <Token Import(target='target')>
2354+
23442355
``NamedTuple`` subclasses can be generic::
23452356

23462357
class Group[T](NamedTuple):

Lib/collections/__init__.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ def __ror__(self, other):
358358
except ImportError:
359359
_tuplegetter = lambda index, doc: property(_itemgetter(index), doc=doc)
360360

361-
def namedtuple(typename, field_names, *, rename=False, defaults=None, module=None):
361+
def namedtuple(typename, field_names, *, rename=False, defaults=None, module=None, _classcell=None):
362362
"""Returns a new subclass of tuple with named fields.
363363
364364
>>> Point = namedtuple('Point', ['x', 'y'])
@@ -469,10 +469,15 @@ def _replace(self, /, **kwds):
469469
_replace.__doc__ = (f'Return a new {typename} object replacing specified '
470470
'fields with new values')
471471

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

476+
def __repr__(self):
477+
return self._repr()
478+
479+
__repr__.__doc__ = _repr.__doc__
480+
476481
def _asdict(self):
477482
'Return a new dict which maps field names to their values.'
478483
return _dict(_zip(self._fields, self))
@@ -486,6 +491,7 @@ def __getnewargs__(self):
486491
__new__,
487492
_make.__func__,
488493
_replace,
494+
_repr,
489495
__repr__,
490496
_asdict,
491497
__getnewargs__,
@@ -503,6 +509,7 @@ def __getnewargs__(self):
503509
'_make': _make,
504510
'__replace__': _replace,
505511
'_replace': _replace,
512+
'_repr': _repr,
506513
'__repr__': __repr__,
507514
'_asdict': _asdict,
508515
'__getnewargs__': __getnewargs__,

Lib/test/test_collections.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,10 +648,12 @@ def test_name_conflicts(self):
648648
def test_repr(self):
649649
A = namedtuple('A', 'x')
650650
self.assertEqual(repr(A(1)), 'A(x=1)')
651+
self.assertEqual(A(2)._repr(), 'A(x=2)')
651652
# repr should show the name of the subclass
652653
class B(A):
653654
pass
654655
self.assertEqual(repr(B(1)), 'B(x=1)')
656+
self.assertEqual(B(2)._repr(), 'B(x=2)')
655657

656658
def test_keyword_only_arguments(self):
657659
# See issue 25628

Lib/test/test_typing.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8135,6 +8135,24 @@ class Group(NamedTuple):
81358135
self.assertIs(type(a), Group)
81368136
self.assertEqual(a, (1, [2]))
81378137

8138+
def test_overridden_repr(self):
8139+
class CustomRepresentation(NamedTuple):
8140+
namedtuple_style: bool
8141+
8142+
def __repr__(self):
8143+
if self.namedtuple_style:
8144+
return f"<namedtuple style {self._repr()}>"
8145+
return f"<tuple style {super().__repr__()}>"
8146+
8147+
namedtuple_style_repr = CustomRepresentation(namedtuple_style=True)
8148+
self.assertEqual(
8149+
repr(namedtuple_style_repr),
8150+
"<namedtuple style CustomRepresentation(namedtuple_style=True)>"
8151+
)
8152+
8153+
tuple_style_repr = CustomRepresentation(namedtuple_style=False)
8154+
self.assertEqual(repr(tuple_style_repr), "<tuple style (False,)>")
8155+
81388156
def test_namedtuple_keyword_usage(self):
81398157
with self.assertWarnsRegex(
81408158
DeprecationWarning,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Added :meth:`~collections.somenamedtuple._repr` method to named tuples for reuse in
2+
custom :meth:`object.__repr__` implementations in
3+
:class:`~typing.NamedTuple` subclasses. Contributed by Bartosz Sławecki.

0 commit comments

Comments
 (0)