From 21585968afd8856e1bcba6d660e9c720c8acf8b8 Mon Sep 17 00:00:00 2001 From: bswck Date: Mon, 27 Jan 2025 12:48:59 +0100 Subject: [PATCH 1/7] Add support for `super()` in `NamedTuple` subclasses --- Doc/library/typing.rst | 3 +++ Lib/collections/__init__.py | 6 +++++- Lib/test/test_typing.py | 19 +++++++++++++++++++ Lib/typing.py | 8 ++++---- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 0fee782121b0af..162fe8eada0e88 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2341,6 +2341,9 @@ types. def __repr__(self) -> str: return f'' + 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`. + ``NamedTuple`` subclasses can be generic:: class Group[T](NamedTuple): diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 78229ac54b80da..aa4ae0802a1614 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']) @@ -508,6 +508,10 @@ def __getnewargs__(self): '__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) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index f002d28df60e9c..ef0750326cd367 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8135,6 +8135,25 @@ 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 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_namedtuple_keyword_usage(self): with self.assertWarnsRegex( DeprecationWarning, diff --git a/Lib/typing.py b/Lib/typing.py index 66570db7a5bd74..6b90356f9b2d07 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -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 @@ -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 From dcdab5ffca4b33ae47dab688755c337038978aa3 Mon Sep 17 00:00:00 2001 From: bswck Date: Mon, 27 Jan 2025 12:09:38 +0100 Subject: [PATCH 2/7] Add `_repr` method to named tuples --- Doc/library/collections.rst | 23 +++++++++++++++++++++-- Doc/library/typing.rst | 11 +++++++++++ Lib/collections/__init__.py | 9 ++++++++- Lib/test/test_collections.py | 2 ++ Lib/test/test_typing.py | 18 ++++++++++++++++++ Lib/typing.py | 2 +- 6 files changed, 61 insertions(+), 4 deletions(-) diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 5b4e445762e076..7e53642c2e105c 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:`_repr` + method, backing the default :meth:`__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 @@ -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 @@ -1064,6 +1080,9 @@ fields: .. versionchanged:: 3.5 Property docstrings became writeable. +.. versionchanged:: 3.13 + Default :func:`_repr` + .. seealso:: * See :class:`typing.NamedTuple` for a way to add type hints for named diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 162fe8eada0e88..7ba87284541016 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2344,6 +2344,17 @@ types. 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 :meth:`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 aa4ae0802a1614..a47d354ebd8b39 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -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 ef0750326cd367..7378ad6a14788e 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8154,6 +8154,24 @@ def count(self, item): 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"" + 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/Lib/typing.py b/Lib/typing.py index 6b90356f9b2d07..eaba35a5c97c04 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -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__'}) From b383fe4ebb829bfc19ce55586aa2e8d946f04b64 Mon Sep 17 00:00:00 2001 From: bswck Date: Mon, 27 Jan 2025 12:59:05 +0100 Subject: [PATCH 3/7] Add news entries --- .../next/Library/2025-01-27-12-55-40.gh-issue-85795.fnGbGS.rst | 2 ++ .../Library/2025-01-27-12-58-51.gh-issue-129343.RMb2ln.rst | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-01-27-12-55-40.gh-issue-85795.fnGbGS.rst create mode 100644 Misc/NEWS.d/next/Library/2025-01-27-12-58-51.gh-issue-129343.RMb2ln.rst diff --git a/Misc/NEWS.d/next/Library/2025-01-27-12-55-40.gh-issue-85795.fnGbGS.rst b/Misc/NEWS.d/next/Library/2025-01-27-12-55-40.gh-issue-85795.fnGbGS.rst new file mode 100644 index 00000000000000..1095cd8d915203 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-01-27-12-55-40.gh-issue-85795.fnGbGS.rst @@ -0,0 +1,2 @@ +Added support for :func:`super` calls in user-defined +:class:`~typing.NamedTuple` methods. Contributed by Bartosz Sławecki. 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..4d7f42e0e33313 --- /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:`~somenamedtuple._repr` method to named tuples for reuse in +custom :meth:`object.__repr__` implementations in +:class:`~typing.NamedTuple` subclasses. Contributed by Bartosz Sławecki. From e311816345a97ffedba3cccaec23dc0abf57d011 Mon Sep 17 00:00:00 2001 From: bswck Date: Mon, 27 Jan 2025 13:01:11 +0100 Subject: [PATCH 4/7] Clean up junk from docs --- Doc/library/collections.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 7e53642c2e105c..06e874c0db6a9a 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -1080,9 +1080,6 @@ fields: .. versionchanged:: 3.5 Property docstrings became writeable. -.. versionchanged:: 3.13 - Default :func:`_repr` - .. seealso:: * See :class:`typing.NamedTuple` for a way to add type hints for named From 4ba6fe157dece3a018f11c91eb261165e1539538 Mon Sep 17 00:00:00 2001 From: bswck Date: Mon, 27 Jan 2025 13:07:10 +0100 Subject: [PATCH 5/7] Work through documentation issues --- Doc/library/collections.rst | 6 +++--- Doc/library/typing.rst | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 06e874c0db6a9a..0b624a652e5101 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -849,9 +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, backing the default :meth:`__repr__`, 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 diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 7ba87284541016..ab2c50b5ff9cc3 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2345,7 +2345,7 @@ types. 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 :meth:`tuple.__repr__`: + as ``super().__repr__`` in a ``NamedTuple`` subclass resolves to ``tuple.__repr__``: class Import(NamedTuple): target: str From d9db0006ec43fa85207de4c4ca29af37a8501868 Mon Sep 17 00:00:00 2001 From: bswck Date: Mon, 27 Jan 2025 13:11:53 +0100 Subject: [PATCH 6/7] Refer to `_repr` in news entry correctly --- .../next/Library/2025-01-27-12-58-51.gh-issue-129343.RMb2ln.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 4d7f42e0e33313..e39c0eea379d3a 100644 --- 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 @@ -1,3 +1,3 @@ -Added :meth:`~somenamedtuple._repr` method to named tuples for reuse in +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. From 269d1d99ea67a3ef81a474f3899e7889c2905adf Mon Sep 17 00:00:00 2001 From: bswck Date: Mon, 27 Jan 2025 13:38:02 +0100 Subject: [PATCH 7/7] Fix code block formatting --- Doc/library/typing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index ab2c50b5ff9cc3..41d567b9a2f94f 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2345,7 +2345,7 @@ types. 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__``: + as ``super().__repr__`` in a ``NamedTuple`` subclass resolves to ``tuple.__repr__``:: class Import(NamedTuple): target: str