Skip to content

Commit dcdab5f

Browse files
committed
Add _repr method to named tuples
1 parent 2158596 commit dcdab5f

File tree

6 files changed

+61
-4
lines changed

6 files changed

+61
-4
lines changed

Doc/library/collections.rst

Lines changed: 21 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:`_repr`
853+
method, backing the default :meth:`__repr__`, which lists the tuple contents
854+
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
@@ -967,6 +968,21 @@ field names, the method and attribute names start with an underscore.
967968
remediation is to cast the result to the desired type:
968969
``OrderedDict(nt._asdict())``.
969970

971+
.. method:: somenamedtuple._repr()
972+
973+
Return a representation of the named tuple contents in a ``name=value`` format.
974+
The default ``__repr__`` implementation uses it to produce the representation.
975+
976+
.. doctest::
977+
978+
>>> p = Point(x=11, y=22)
979+
>>> p._repr()
980+
'Point(x=11, y=22)'
981+
>>> p
982+
Point(x=11, y=22)
983+
984+
.. versionadded:: 3.14
985+
970986
.. method:: somenamedtuple._replace(**kwargs)
971987

972988
Return a new instance of the named tuple replacing specified fields with new
@@ -1064,6 +1080,9 @@ fields:
10641080
.. versionchanged:: 3.5
10651081
Property docstrings became writeable.
10661082

1083+
.. versionchanged:: 3.13
1084+
Default :func:`_repr`
1085+
10671086
.. seealso::
10681087

10691088
* See :class:`typing.NamedTuple` for a way to add type hints for named

Doc/library/typing.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2344,6 +2344,17 @@ types.
23442344
Calls to :func:`super` are supported inside user-defined methods of ``NamedTuple`` subclasses
23452345
to reuse functionality from built-in classes :class:`tuple` and :class:`object`.
23462346

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

23492360
class Group[T](NamedTuple):

Lib/collections/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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
@@ -8154,6 +8154,24 @@ def count(self, item):
81548154
self.assertEqual(aspiring_triager.count("Bartosz"), 1)
81558155
self.assertEqual(aspiring_triager.count("Peter"), -1) # already a triager!
81568156

8157+
def test_overridden_repr(self):
8158+
class CustomRepresentation(NamedTuple):
8159+
namedtuple_style: bool
8160+
8161+
def __repr__(self):
8162+
if self.namedtuple_style:
8163+
return f"<namedtuple style {self._repr()}>"
8164+
return f"<tuple style {super().__repr__()}>"
8165+
8166+
namedtuple_style_repr = CustomRepresentation(namedtuple_style=True)
8167+
self.assertEqual(
8168+
repr(namedtuple_style_repr),
8169+
"<namedtuple style CustomRepresentation(namedtuple_style=True)>"
8170+
)
8171+
8172+
tuple_style_repr = CustomRepresentation(namedtuple_style=False)
8173+
self.assertEqual(repr(tuple_style_repr), "<tuple style (False,)>")
8174+
81578175
def test_namedtuple_keyword_usage(self):
81588176
with self.assertWarnsRegex(
81598177
DeprecationWarning,

Lib/typing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2953,7 +2953,7 @@ def annotate(format):
29532953
# attributes prohibited to set in NamedTuple class syntax
29542954
_prohibited = frozenset({'__new__', '__init__', '__slots__', '__getnewargs__',
29552955
'_fields', '_field_defaults',
2956-
'_make', '_replace', '_asdict', '_source'})
2956+
'_make', '_replace', '_repr', '_asdict', '_source'})
29572957

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

0 commit comments

Comments
 (0)