From c4397f65a7ab23ce3930ea5a8899e0b4a40478cd Mon Sep 17 00:00:00 2001 From: bswck Date: Mon, 27 Jan 2025 14:16:24 +0100 Subject: [PATCH] Add `_repr` method to named tuples --- Doc/library/collections.rst | 20 +++++++++++++++++-- Doc/library/typing.rst | 11 ++++++++++ Lib/collections/__init__.py | 11 ++++++++-- Lib/test/test_collections.py | 2 ++ Lib/test/test_typing.py | 18 +++++++++++++++++ ...-01-27-12-58-51.gh-issue-129343.RMb2ln.rst | 3 +++ 6 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-01-27-12-58-51.gh-issue-129343.RMb2ln.rst diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 5b4e445762e076..f260ba37697079 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -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 @@ -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 diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 0fee782121b0af..08b4549d351439 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2341,6 +2341,17 @@ types. def __repr__(self) -> str: return f'' + 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'' # + ``NamedTuple`` subclasses can be generic:: class Group[T](NamedTuple): diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 78229ac54b80da..dda2d770b42e46 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -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']) @@ -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)) @@ -486,6 +491,7 @@ def __getnewargs__(self): __new__, _make.__func__, _replace, + _repr, __repr__, _asdict, __getnewargs__, @@ -503,6 +509,7 @@ def __getnewargs__(self): '_make': _make, '__replace__': _replace, '_replace': _replace, + '_repr': _repr, '__repr__': __repr__, '_asdict': _asdict, '__getnewargs__': __getnewargs__, diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 1e93530398be79..a6b9a5297bb8e8 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -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 diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index f002d28df60e9c..c6f3b8f557a537 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -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"" + return f"" + + namedtuple_style_repr = CustomRepresentation(namedtuple_style=True) + self.assertEqual( + repr(namedtuple_style_repr), + "" + ) + + tuple_style_repr = CustomRepresentation(namedtuple_style=False) + self.assertEqual(repr(tuple_style_repr), "") + def test_namedtuple_keyword_usage(self): with self.assertWarnsRegex( DeprecationWarning, diff --git a/Misc/NEWS.d/next/Library/2025-01-27-12-58-51.gh-issue-129343.RMb2ln.rst b/Misc/NEWS.d/next/Library/2025-01-27-12-58-51.gh-issue-129343.RMb2ln.rst new file mode 100644 index 00000000000000..e39c0eea379d3a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-01-27-12-58-51.gh-issue-129343.RMb2ln.rst @@ -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.