Skip to content

Commit 027f629

Browse files
committed
pythongh-135228: When @DataClass(slots=True) replaces a dataclass, make the original class collectible
An interesting hack, but more localized in scope than python#135230. This may be a breaking change if people intentionally keep the original class around when using `@dataclass(slots=True)`, and then use `__dict__` or `__weakref__` on the original class.
1 parent aec7f5f commit 027f629

File tree

3 files changed

+52
-0
lines changed

3 files changed

+52
-0
lines changed

Lib/dataclasses.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,6 +1338,11 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields):
13381338
or _update_func_cell_for__class__(member.fdel, cls, newcls)):
13391339
break
13401340

1341+
# gh-135228: Make sure the original class can be garbage collected.
1342+
old_cls_dict = cls.__dict__ | _deproxier
1343+
old_cls_dict.pop('__weakref__', None)
1344+
old_cls_dict.pop('__dict__', None)
1345+
13411346
return newcls
13421347

13431348

@@ -1732,3 +1737,11 @@ def _replace(self, /, **changes):
17321737
# changes that aren't fields, this will correctly raise a
17331738
# TypeError.
17341739
return self.__class__(**changes)
1740+
1741+
1742+
# Hack to the get the underlying dict out of a mappingproxy
1743+
# Use it with: cls.__dict__ | _deproxier
1744+
class _Deproxier:
1745+
def __ror__(self, other):
1746+
return other
1747+
_deproxier = _Deproxier()

Lib/test/test_dataclasses/__init__.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3804,6 +3804,41 @@ class WithCorrectSuper(CorrectSuper):
38043804
# that we create internally.
38053805
self.assertEqual(CorrectSuper.args, ["default", "default"])
38063806

3807+
def test_original_class_is_gced(self):
3808+
# gh-135228: Make sure when we replace the class with slots=True, the original class
3809+
# gets garbage collected.
3810+
def make_simple():
3811+
@dataclass(slots=True)
3812+
class SlotsTest:
3813+
pass
3814+
3815+
return SlotsTest
3816+
3817+
def make_with_annotations():
3818+
@dataclass(slots=True)
3819+
class SlotsTest:
3820+
x: int
3821+
3822+
return SlotsTest
3823+
3824+
def make_with_annotations_and_method():
3825+
@dataclass(slots=True)
3826+
class SlotsTest:
3827+
x: int
3828+
3829+
def method(self) -> int:
3830+
return self.x
3831+
3832+
return SlotsTest
3833+
3834+
for make in (make_simple, make_with_annotations, make_with_annotations_and_method):
3835+
with self.subTest(make=make):
3836+
C = make()
3837+
support.gc_collect()
3838+
candidates = [cls for cls in object.__subclasses__() if cls.__name__ == 'SlotsTest'
3839+
and cls.__firstlineno__ == make.__code__.co_firstlineno + 1]
3840+
self.assertEqual(candidates, [C])
3841+
38073842

38083843
class TestDescriptors(unittest.TestCase):
38093844
def test_set_name(self):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
When :mod:`dataclasses` replaces a class with a slotted dataclass, the
2+
original class is now garbage collected again. Earlier changes in Python
3+
3.14 caused this class to remain in existence together with the replacement
4+
class synthesized by :mod:`dataclasses`.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy