Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

gh-90562: Fix super() without args calls for dataclasses with slots #111538

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
Loading
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
gh-111500: Fix super() without args calls for dataclasses with slots
  • Loading branch information
sobolevn committed Oct 31, 2023
commit d3101658ba9f7b26f859cfea4ab62ea88a9e673c
29 changes: 29 additions & 0 deletions 29 Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,7 @@ def _add_slots(cls, is_frozen, weakref_slot):

# And finally create the class.
qualname = getattr(cls, '__qualname__', None)
old_cls = cls
cls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
if qualname is not None:
cls.__qualname__ = qualname
Expand All @@ -1243,6 +1244,34 @@ def _add_slots(cls, is_frozen, weakref_slot):
if '__setstate__' not in cls_dict:
cls.__setstate__ = _dataclass_setstate

# The following is a fix for
# https://github.com/python/cpython/issues/111500
# The code is copied-and-modified from https://github.com/python-attrs/attrs
# All credits for it goes to the `attrs` team and contributors.
#
# If a method mentions `__class__` or uses the no-arg super(), the
# compiler will bake a reference to the class in the method itself
# as `method.__closure__`. Since we replace the class with a
# clone, we rewrite these references so it keeps working.
for item in cls.__dict__.values():
sobolevn marked this conversation as resolved.
Show resolved Hide resolved
if isinstance(item, (classmethod, staticmethod)):
closure_cells = getattr(item.__func__, "__closure__", None)
Comment on lines +1238 to +1239
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

classmethod and staticmethod have the __wrapped__ attribute since 3.10, so this code is perhaps dead.

elif isinstance(item, property):
closure_cells = getattr(item.fget, "__closure__", None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if a getter doesn't have a closure, but a setter or deleter does?

else:
closure_cells = getattr(item, "__closure__", None)

if not closure_cells:
continue
for cell in closure_cells:
try:
match = cell.cell_contents is old_cls
except ValueError: # Cell is empty
pass
else:
if match:
cell.cell_contents = cls

return cls


Expand Down
61 changes: 61 additions & 0 deletions 61 Lib/test/test_dataclasses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3494,6 +3494,67 @@ class A(Base):
a_ref = weakref.ref(a)
self.assertIs(a.__weakref__, a_ref)

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):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix ``super()`` call without arguments for :mod:`dataclasses` with
``slots=True``.
Morty Proxy This is a proxified and sanitized view of the page, visit original site.