An interesting hack, but more localized in scope than #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.
Co-authored-by: Alyssa Coghlan <ncoghlan@gmail.com>
or _update_func_cell_for__class__(member.fdel, cls, newcls)):
break
+ # gh-135228: Make sure the original class can be garbage collected.
+ # Bypass mapping proxy to allow __dict__ to be removed
+ old_cls_dict = cls.__dict__ | _deproxier
+ old_cls_dict.pop('__dict__', None)
+ if "__weakref__" in cls.__dict__:
+ del cls.__weakref__
+
return newcls
# changes that aren't fields, this will correctly raise a
# TypeError.
return self.__class__(**changes)
+
+
+# Hack to the get the underlying dict out of a mappingproxy
+# Use it with: cls.__dict__ | _deproxier
+class _Deproxier:
+ def __ror__(self, other):
+ return other
+_deproxier = _Deproxier()
# that we create internally.
self.assertEqual(CorrectSuper.args, ["default", "default"])
+ def test_original_class_is_gced(self):
+ # gh-135228: Make sure when we replace the class with slots=True, the original class
+ # gets garbage collected.
+ def make_simple():
+ @dataclass(slots=True)
+ class SlotsTest:
+ pass
+
+ return SlotsTest
+
+ def make_with_annotations():
+ @dataclass(slots=True)
+ class SlotsTest:
+ x: int
+
+ return SlotsTest
+
+ def make_with_annotations_and_method():
+ @dataclass(slots=True)
+ class SlotsTest:
+ x: int
+
+ def method(self) -> int:
+ return self.x
+
+ return SlotsTest
+
+ for make in (make_simple, make_with_annotations, make_with_annotations_and_method):
+ with self.subTest(make=make):
+ C = make()
+ support.gc_collect()
+ candidates = [cls for cls in object.__subclasses__() if cls.__name__ == 'SlotsTest'
+ and cls.__firstlineno__ == make.__code__.co_firstlineno + 1]
+ self.assertEqual(candidates, [C])
+
class TestDescriptors(unittest.TestCase):
def test_set_name(self):
--- /dev/null
+When :mod:`dataclasses` replaces a class with a slotted dataclass, the
+original class is now garbage collected again. Earlier changes in Python
+3.14 caused this class to remain in existence together with the replacement
+class synthesized by :mod:`dataclasses`.