From e84d487dd665dd23d1efff9bf127b89e548e5586 Mon Sep 17 00:00:00 2001 From: bswck Date: Mon, 27 Jan 2025 14:02:18 +0100 Subject: [PATCH 01/16] 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 ++++---- ...5-01-27-12-55-40.gh-issue-85795.fnGbGS.rst | 2 ++ 5 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-01-27-12-55-40.gh-issue-85795.fnGbGS.rst 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 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. From 577bdc7e3ff16ddb1badf76ecf59f5bdd02262c2 Mon Sep 17 00:00:00 2001 From: bswck Date: Tue, 28 Jan 2025 05:26:35 +0100 Subject: [PATCH 02/16] Rewrite the test to be more neutral --- Lib/test/test_typing.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index ef0750326cd367..7606218979d1cc 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8137,22 +8137,23 @@ class Group(NamedTuple): def test_classcell_access(self): # See #85795: __class__ not set defining 'X' as - class AspiringTriager(NamedTuple): - name: str = "Bartosz" + class Pointer(NamedTuple): + address: int + target_type = "int" @property - def tablename(self): - return __class__.__name__.lower() + "s" + def typename(self): + return __class__.target_type.__name__ 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! + if item == 0: + return -1 + return super().count(self.address) + + ptr = Pointer(0xdeadbeef) + self.assertEqual(ptr.typename, "int") + self.assertEqual(ptr.count(0), -1) + self.assertEqual(ptr.count(0xdeadbeef), 1) def test_namedtuple_keyword_usage(self): with self.assertWarnsRegex( From b77ea9f5c2cdb165fd61c3da533da06f93d9173a Mon Sep 17 00:00:00 2001 From: bswck Date: Tue, 28 Jan 2025 05:32:59 +0100 Subject: [PATCH 03/16] Add a check that `__classcell__` isn't leaked --- Lib/test/test_typing.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 7606218979d1cc..b559f20c23621d 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8155,6 +8155,10 @@ def count(self, item): self.assertEqual(ptr.count(0), -1) self.assertEqual(ptr.count(0xdeadbeef), 1) + with self.assertRaises(AttributeError): + # __classcell__ should never be leaked into end classes + Pointer.__classcell__ + def test_namedtuple_keyword_usage(self): with self.assertWarnsRegex( DeprecationWarning, From 31f66073e2498477a1c1a578644c5c7697896846 Mon Sep 17 00:00:00 2001 From: bswck Date: Tue, 28 Jan 2025 05:37:46 +0100 Subject: [PATCH 04/16] Separate leakage test and mark it as CPython-only --- Lib/test/test_typing.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index b559f20c23621d..95e7ed780e8999 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8135,7 +8135,7 @@ class Group(NamedTuple): self.assertIs(type(a), Group) self.assertEqual(a, (1, [2])) - def test_classcell_access(self): + def test_super_works_in_namedtuples(self): # See #85795: __class__ not set defining 'X' as class Pointer(NamedTuple): address: int @@ -8155,9 +8155,16 @@ def count(self, item): self.assertEqual(ptr.count(0), -1) self.assertEqual(ptr.count(0xdeadbeef), 1) + @cpython_only + def test_classcell_not_leaked(self): + # __classcell__ should never be leaked into end classes + + class Spam(NamedTuple): + lambda: super() + lambda: __class__ + with self.assertRaises(AttributeError): - # __classcell__ should never be leaked into end classes - Pointer.__classcell__ + Spam.__classcell__ def test_namedtuple_keyword_usage(self): with self.assertWarnsRegex( From 74578e9e4cc0ff0916e08eb38da175d08e81d668 Mon Sep 17 00:00:00 2001 From: bswck Date: Tue, 28 Jan 2025 05:40:01 +0100 Subject: [PATCH 05/16] Shorten the test name, we know it's a namedtuple test from context --- Lib/test/test_typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 95e7ed780e8999..69b62f45ad591d 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8135,7 +8135,7 @@ class Group(NamedTuple): self.assertIs(type(a), Group) self.assertEqual(a, (1, [2])) - def test_super_works_in_namedtuples(self): + def test_super_works(self): # See #85795: __class__ not set defining 'X' as class Pointer(NamedTuple): address: int From 7696b87b728b9a2dff65535a1781aa34a35c4cdb Mon Sep 17 00:00:00 2001 From: bswck Date: Tue, 28 Jan 2025 05:40:37 +0100 Subject: [PATCH 06/16] We test for dunder class too --- Lib/test/test_typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 69b62f45ad591d..d9b07c0d5bda01 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8135,7 +8135,7 @@ class Group(NamedTuple): self.assertIs(type(a), Group) self.assertEqual(a, (1, [2])) - def test_super_works(self): + def test_super_and_dunder_class_works(self): # See #85795: __class__ not set defining 'X' as class Pointer(NamedTuple): address: int From 2f801dc4c5f063e4ed6bf8c5cc47e3404e62af6e Mon Sep 17 00:00:00 2001 From: bswck Date: Tue, 28 Jan 2025 05:40:56 +0100 Subject: [PATCH 07/16] Language correctness --- Lib/test/test_typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index d9b07c0d5bda01..809a049a9b285c 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8135,7 +8135,7 @@ class Group(NamedTuple): self.assertIs(type(a), Group) self.assertEqual(a, (1, [2])) - def test_super_and_dunder_class_works(self): + def test_super_and_dunder_class_work(self): # See #85795: __class__ not set defining 'X' as class Pointer(NamedTuple): address: int From f8cf62f1516996dccfb93ec8cd6d4d88c429d646 Mon Sep 17 00:00:00 2001 From: bswck Date: Tue, 28 Jan 2025 05:47:17 +0100 Subject: [PATCH 08/16] Minor aesthetical --- Lib/test/test_typing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 809a049a9b285c..a27286feb2367c 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8137,6 +8137,7 @@ class Group(NamedTuple): def test_super_and_dunder_class_work(self): # See #85795: __class__ not set defining 'X' as + class Pointer(NamedTuple): address: int target_type = "int" @@ -8157,7 +8158,7 @@ def count(self, item): @cpython_only def test_classcell_not_leaked(self): - # __classcell__ should never be leaked into end classes + # __classcell__ should never leak into end classes class Spam(NamedTuple): lambda: super() From cc20a2d5a0c930545a49c0f197cd6b2b519f3e10 Mon Sep 17 00:00:00 2001 From: bswck Date: Tue, 28 Jan 2025 05:55:16 +0100 Subject: [PATCH 09/16] Remove unintended attribute access --- Lib/test/test_typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index a27286feb2367c..ee56442983f64d 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8144,7 +8144,7 @@ class Pointer(NamedTuple): @property def typename(self): - return __class__.target_type.__name__ + return __class__.target_type def count(self, item): if item == 0: From efb9ad203aa49871904c7628dbf40b06c9e16bd0 Mon Sep 17 00:00:00 2001 From: bswck Date: Wed, 29 Jan 2025 10:54:05 +0100 Subject: [PATCH 10/16] Encapsulate the `_classcell` parameter --- Lib/collections/__init__.py | 53 +++++++++++++++++++------------------ Lib/typing.py | 10 +++---- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index aa4ae0802a1614..7966e4955eda71 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -358,30 +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, _classcell=None): - """Returns a new subclass of tuple with named fields. - - >>> Point = namedtuple('Point', ['x', 'y']) - >>> Point.__doc__ # docstring for the new class - 'Point(x, y)' - >>> p = Point(11, y=22) # instantiate with positional args or keywords - >>> p[0] + p[1] # indexable like a plain tuple - 33 - >>> x, y = p # unpack like a regular tuple - >>> x, y - (11, 22) - >>> p.x + p.y # fields also accessible by name - 33 - >>> d = p._asdict() # convert to a dictionary - >>> d['x'] - 11 - >>> Point(**d) # convert from a dictionary - Point(x=11, y=22) - >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields - Point(x=100, y=22) - - """ - +def _namedtuple(typename, field_names, *, rename=False, defaults=None, module=None, classcell=None): # Validate the field names. At the user's option, either generate an error # message or automatically replace the field name with a valid name. if isinstance(field_names, str): @@ -509,8 +486,8 @@ def __getnewargs__(self): '__match_args__': field_names, } - if _classcell is not None: - class_namespace["__classcell__"] = _classcell + 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}') @@ -536,6 +513,30 @@ def __getnewargs__(self): return result +def namedtuple(typename, field_names, *, rename=False, defaults=None, module=None): + """Returns a new subclass of tuple with named fields. + + >>> Point = namedtuple('Point', ['x', 'y']) + >>> Point.__doc__ # docstring for the new class + 'Point(x, y)' + >>> p = Point(11, y=22) # instantiate with positional args or keywords + >>> p[0] + p[1] # indexable like a plain tuple + 33 + >>> x, y = p # unpack like a regular tuple + >>> x, y + (11, 22) + >>> p.x + p.y # fields also accessible by name + 33 + >>> d = p._asdict() # convert to a dictionary + >>> d['x'] + 11 + >>> Point(**d) # convert from a dictionary + Point(x=11, y=22) + >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields + Point(x=100, y=22) + + """ + return _namedtuple(typename, field_names, rename=rename, defaults=defaults, module=module) ######################################################################## ### Counter diff --git a/Lib/typing.py b/Lib/typing.py index 6b90356f9b2d07..5953ebc9d5a4f5 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 = (), _classcell=None): - nm_tpl = collections.namedtuple(name, fields, defaults=defaults, - module=module, _classcell=_classcell) +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 @@ -2999,8 +2999,8 @@ def annotate(format): f"{'s' if len(default_names) > 1 else ''} " f"{', '.join(default_names)}") nm_tpl = _make_nmtuple(typename, field_names, annotate, - defaults=[ns[n] for n in default_names], - module=ns['__module__'], _classcell=ns.pop("__classcell__", None)) + defaults=[ns[n] for n in default_names], module=ns['__module__'], + classcell=ns.pop('__classcell__', None)) nm_tpl.__bases__ = bases if Generic in bases: class_getitem = _generic_class_getitem From d9cdb27ee580d9a65e246b3af2e20bada84ce7a9 Mon Sep 17 00:00:00 2001 From: bswck Date: Wed, 29 Jan 2025 10:54:54 +0100 Subject: [PATCH 11/16] Use a sentinel for missing `__classcell__` --- Lib/collections/__init__.py | 6 ++++-- Lib/typing.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 7966e4955eda71..dc92a931b0edf9 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -358,7 +358,9 @@ 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, classcell=None): +_nmtuple_classcell_sentinel = object() + +def _namedtuple(typename, field_names, *, rename=False, defaults=None, module=None, classcell=_nmtuple_classcell_sentinel): # Validate the field names. At the user's option, either generate an error # message or automatically replace the field name with a valid name. if isinstance(field_names, str): @@ -486,7 +488,7 @@ def __getnewargs__(self): '__match_args__': field_names, } - if classcell is not None: + if classcell is not _nmtuple_classcell_sentinel: class_namespace["__classcell__"] = classcell for index, name in enumerate(field_names): diff --git a/Lib/typing.py b/Lib/typing.py index 5953ebc9d5a4f5..70bf4aa6dd8b09 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2929,7 +2929,7 @@ def __round__(self, ndigits: int = 0) -> T: pass -def _make_nmtuple(name, fields, annotate_func, module, defaults = (), classcell=None): +def _make_nmtuple(name, fields, annotate_func, module, defaults = (), classcell=collections._nmtuple_classcell_sentinel): nm_tpl = collections._namedtuple(name, fields, defaults=defaults, module=module, classcell=classcell) nm_tpl.__annotate__ = nm_tpl.__new__.__annotate__ = annotate_func @@ -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__'], - classcell=ns.pop('__classcell__', None)) + classcell=ns.pop('__classcell__', collections._nmtuple_classcell_sentinel)) nm_tpl.__bases__ = bases if Generic in bases: class_getitem = _generic_class_getitem From 074df4c64bf13d6b8bd2f52ef4b435ab71ef8c8e Mon Sep 17 00:00:00 2001 From: bswck Date: Wed, 29 Jan 2025 10:58:31 +0100 Subject: [PATCH 12/16] Convert new documentation into a `versionchanged` block --- Doc/library/typing.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 162fe8eada0e88..3e108a072a14f6 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2341,9 +2341,6 @@ 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): @@ -2392,6 +2389,11 @@ types. disallowed in Python 3.15. To create a NamedTuple class with 0 fields, use ``class NT(NamedTuple): pass`` or ``NT = NamedTuple("NT", [])``. + .. versionchanged:: 3.14 + Added support for calls to :func:`super` inside user-defined methods + of ``NamedTuple`` subclasses to reuse functionality from built-in classes + :class:`tuple` and :class:`object`. + .. class:: NewType(name, tp) Helper class to create low-overhead :ref:`distinct types `. From 998474d3cc782a2bafe86837e2184454be7cde6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20S=C5=82awecki?= Date: Wed, 29 Jan 2025 13:30:11 +0100 Subject: [PATCH 13/16] Add a comment discussing reasons for `__classcell__` manipulation Co-authored-by: Alex Waygood --- Lib/collections/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index dc92a931b0edf9..8a59e10fc97ada 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -488,6 +488,8 @@ def __getnewargs__(self): '__match_args__': field_names, } + # gh-85795: `super()` calls inside `typing.NamedTuple` methods will not + # work unless `__classcell__` is propagated by `collections._namedtuple` if classcell is not _nmtuple_classcell_sentinel: class_namespace["__classcell__"] = classcell From ee93519bccb4914b41ab2c3b51fc3813edce693d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20S=C5=82awecki?= Date: Wed, 29 Jan 2025 13:31:50 +0100 Subject: [PATCH 14/16] Reformat for optimal line length Co-authored-by: Alex Waygood --- Lib/collections/__init__.py | 3 ++- Lib/typing.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 8a59e10fc97ada..09e0ce159e84d5 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -360,7 +360,8 @@ def __ror__(self, other): _nmtuple_classcell_sentinel = object() -def _namedtuple(typename, field_names, *, rename=False, defaults=None, module=None, classcell=_nmtuple_classcell_sentinel): +def _namedtuple(typename, field_names, *, rename=False, defaults=None, module=None, + classcell=_nmtuple_classcell_sentinel): # Validate the field names. At the user's option, either generate an error # message or automatically replace the field name with a valid name. if isinstance(field_names, str): diff --git a/Lib/typing.py b/Lib/typing.py index 70bf4aa6dd8b09..9c62a979131085 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2929,7 +2929,8 @@ def __round__(self, ndigits: int = 0) -> T: pass -def _make_nmtuple(name, fields, annotate_func, module, defaults = (), classcell=collections._nmtuple_classcell_sentinel): +def _make_nmtuple(name, fields, annotate_func, module, defaults = (), + classcell=collections._nmtuple_classcell_sentinel): nm_tpl = collections._namedtuple(name, fields, defaults=defaults, module=module, classcell=classcell) nm_tpl.__annotate__ = nm_tpl.__new__.__annotate__ = annotate_func From e3fc197f39cb5fa2bfedd1aa4cfac5efc537c3d3 Mon Sep 17 00:00:00 2001 From: bswck Date: Wed, 29 Jan 2025 13:53:33 +0100 Subject: [PATCH 15/16] Add extra param `stack_offset` for fetching correct frames --- Lib/collections/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 09e0ce159e84d5..3bcc0a495a80d9 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -361,7 +361,7 @@ def __ror__(self, other): _nmtuple_classcell_sentinel = object() def _namedtuple(typename, field_names, *, rename=False, defaults=None, module=None, - classcell=_nmtuple_classcell_sentinel): + classcell=_nmtuple_classcell_sentinel, stack_offset=1): # Validate the field names. At the user's option, either generate an error # message or automatically replace the field name with a valid name. if isinstance(field_names, str): @@ -507,10 +507,10 @@ def __getnewargs__(self): # specified a particular module. if module is None: try: - module = _sys._getframemodulename(1) or '__main__' + module = _sys._getframemodulename(stack_offset) or '__main__' except AttributeError: try: - module = _sys._getframe(1).f_globals.get('__name__', '__main__') + module = _sys._getframe(stack_offset).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): pass if module is not None: @@ -541,7 +541,8 @@ def namedtuple(typename, field_names, *, rename=False, defaults=None, module=Non Point(x=100, y=22) """ - return _namedtuple(typename, field_names, rename=rename, defaults=defaults, module=module) + return _namedtuple(typename, field_names, rename=rename, defaults=defaults, module=module, + stack_offset=2) ######################################################################## ### Counter From c2a0c5067e084fd84532adb99dcef73619f091e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20S=C5=82awecki?= Date: Thu, 30 Jan 2025 00:13:54 +0100 Subject: [PATCH 16/16] Use `next` version ref instead of `3.14` Co-authored-by: Peter Bierma --- 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 3e108a072a14f6..7c742d9ad9c620 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2389,7 +2389,7 @@ types. disallowed in Python 3.15. To create a NamedTuple class with 0 fields, use ``class NT(NamedTuple): pass`` or ``NT = NamedTuple("NT", [])``. - .. versionchanged:: 3.14 + .. versionchanged:: next Added support for calls to :func:`super` inside user-defined methods of ``NamedTuple`` subclasses to reuse functionality from built-in classes :class:`tuple` and :class:`object`.