From 994fd502bda9e234d66176124df1209f589d1479 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 31 Oct 2023 14:20:34 +0300 Subject: [PATCH 1/3] gh-111500: Fix `super()` without args calls for `dataclasses` with slots --- Lib/test/test_dataclasses/__init__.py | 61 +++++++++++++++++++ ...-10-31-14-19-50.gh-issue-111500.y914Ti.rst | 2 + 2 files changed, 63 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-10-31-14-19-50.gh-issue-111500.y914Ti.rst diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index 2b4defe35f8a33d..a4f60d8d91a1263 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -4193,6 +4193,67 @@ class SlotsTest: and cls.__firstlineno__ == make.__code__.co_firstlineno + 1] self.assertEqual(candidates, [C]) + def test_super_without_params_and_slots(self): + # https://github.com/python/cpython/issues/111500 + for slots in (True, False): + with self.subTest(slots=slots): + @dataclass(slots=slots) + class Base: + x: int = 0 + def __post_init__(self): + self.x = 1 + def method(self): + return 2 + @property + def prop(self): + return 3 + @classmethod + def clsmethod(cls): + return 4 + @staticmethod + def stmethod(): + return 5 + + @dataclass(slots=slots) + class Child(Base): + y: int = 0 + z: int = 0 + def __post_init__(self): + self.y = 2 + super().__post_init__() + self.z = 3 + def method(self): + return super().method() + @property + def prop(self): + return super().prop + @classmethod + def clsmethod(cls): + return super().clsmethod() + @staticmethod + def stmethod1(): + return super(Child, Child).stmethod() + @staticmethod + def stmethod2(): + return super().stmethod() + + inst = Child() + self.assertEqual(inst.x, 1) + self.assertEqual(inst.y, 2) + self.assertEqual(inst.z, 3) + self.assertEqual(inst.method(), 2) + self.assertEqual(inst.prop, 3) + self.assertEqual(inst.clsmethod(), 4) + self.assertEqual(Child.clsmethod(), 4) + self.assertEqual(inst.stmethod1(), 5) + self.assertEqual(Child.stmethod1(), 5) + # These failures match regular classes: + msg = r"super\(\): no arguments" + with self.assertRaisesRegex(RuntimeError, msg): + inst.stmethod2() + with self.assertRaisesRegex(RuntimeError, msg): + Child.stmethod2() + class TestDescriptors(unittest.TestCase): def test_set_name(self): diff --git a/Misc/NEWS.d/next/Library/2023-10-31-14-19-50.gh-issue-111500.y914Ti.rst b/Misc/NEWS.d/next/Library/2023-10-31-14-19-50.gh-issue-111500.y914Ti.rst new file mode 100644 index 000000000000000..c88e3b296c61adf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-31-14-19-50.gh-issue-111500.y914Ti.rst @@ -0,0 +1,2 @@ +Fix ``super()`` call without arguments for :mod:`dataclasses` with +``slots=True``. From e6249655e1c14ad4d02efd2d1b7c696c18e060db Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 12 Mar 2024 22:15:44 +0300 Subject: [PATCH 2/3] Looks like we don't need to unwrap anything --- Lib/test/test_dataclasses/__init__.py | 88 ++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index a4f60d8d91a1263..937bc0efd27c08e 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -21,7 +21,7 @@ from typing import get_type_hints from collections import deque, OrderedDict, namedtuple, defaultdict from copy import deepcopy -from functools import total_ordering, wraps +from functools import total_ordering, wraps, cache import typing # Needed for the string "typing.ClassVar[int]" to work as an annotation. import dataclasses # Needed for the string "dataclasses.InitVar[int]" to work as an annotation. @@ -4195,6 +4195,13 @@ class SlotsTest: def test_super_without_params_and_slots(self): # https://github.com/python/cpython/issues/111500 + + def decorator(func): + @wraps(func) + def inner(*args, **kwargs): + return func(*args, **kwargs) + return inner + for slots in (True, False): with self.subTest(slots=slots): @dataclass(slots=slots) @@ -4204,15 +4211,18 @@ def __post_init__(self): self.x = 1 def method(self): return 2 + @decorator + def decorated(self): + return 3 @property def prop(self): - return 3 + return 4 @classmethod def clsmethod(cls): - return 4 + return 5 @staticmethod def stmethod(): - return 5 + return 6 @dataclass(slots=slots) class Child(Base): @@ -4224,6 +4234,11 @@ def __post_init__(self): self.z = 3 def method(self): return super().method() + def decorated1(self): + return super().decorated() + @decorator + def decorated2(self): + return super().decorated() @property def prop(self): return super().prop @@ -4236,17 +4251,21 @@ def stmethod1(): @staticmethod def stmethod2(): return super().stmethod() + def cached1(self): + return super().cached() inst = Child() self.assertEqual(inst.x, 1) self.assertEqual(inst.y, 2) self.assertEqual(inst.z, 3) self.assertEqual(inst.method(), 2) - self.assertEqual(inst.prop, 3) - self.assertEqual(inst.clsmethod(), 4) - self.assertEqual(Child.clsmethod(), 4) - self.assertEqual(inst.stmethod1(), 5) - self.assertEqual(Child.stmethod1(), 5) + self.assertEqual(inst.decorated1(), 3) + self.assertEqual(inst.decorated2(), 3) + self.assertEqual(inst.prop, 4) + self.assertEqual(inst.clsmethod(), 5) + self.assertEqual(Child.clsmethod(), 5) + self.assertEqual(inst.stmethod1(), 6) + self.assertEqual(Child.stmethod1(), 6) # These failures match regular classes: msg = r"super\(\): no arguments" with self.assertRaisesRegex(RuntimeError, msg): @@ -4254,6 +4273,57 @@ def stmethod2(): with self.assertRaisesRegex(RuntimeError, msg): Child.stmethod2() + def test_super_without_params_wrapped(self): + for slots in (True, False): + with self.subTest(slots=slots): + @dataclass(slots=slots, frozen=True) + class Base: + @cache + def cached(self): + return 1 + def regular(self): + return 2 + @classmethod + @cache + def cached_cl(cls): + return 3 + @classmethod + def regular_cl(cls): + return 4 + + @dataclass(slots=slots, frozen=True) + class Child(Base): + def cached1(self): + return super().cached() + 10 + @cache + def cached2(self): + return super().cached() + 20 + @cache + def cached3(self): + return super().regular() + 30 + @classmethod + @cache + def cached_cl1(cls): + return super().cached_cl() + 40 + @classmethod + def cached_cl2(cls): + return super().cached_cl() + 50 + @classmethod + @cache + def cached_cl3(cls): + return super().regular_cl() + 60 + + inst = Child() + self.assertEqual(inst.cached(), 1) + self.assertEqual(inst.cached1(), 11) + self.assertEqual(inst.cached2(), 21) + self.assertEqual(inst.cached3(), 32) + self.assertEqual(inst.cached_cl(), 3) + self.assertEqual(inst.regular_cl(), 4) + self.assertEqual(inst.cached_cl1(), 43) + self.assertEqual(inst.cached_cl2(), 53) + self.assertEqual(inst.cached_cl3(), 64) + class TestDescriptors(unittest.TestCase): def test_set_name(self): From 65e2b18b02b7590f9d1fd4eff70e0b4995bebd0d Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 12 Mar 2024 23:05:49 +0300 Subject: [PATCH 3/3] Address review --- Lib/test/test_dataclasses/__init__.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index 937bc0efd27c08e..0a9d0f4ce7b5985 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -4324,6 +4324,28 @@ def cached_cl3(cls): self.assertEqual(inst.cached_cl2(), 53) self.assertEqual(inst.cached_cl3(), 64) + def test_super_without_params_single_wrapper(self): + def decorator(func): + @wraps(func) + def inner(*args, **kwargs): + return func(*args, **kwargs) + return inner + + for slots in (True, False): + with self.subTest(slots=slots): + @dataclass(slots=True) + class A: + def method(self): + return 1 + + @dataclass(slots=True) + class B(A): + @decorator + def method(self): + return super().method() + + self.assertEqual(B().method(), 1) + class TestDescriptors(unittest.TestCase): def test_set_name(self):