https://github.com/python/cpython/commit/46cbdf967ada11b0286060488b61635fd6a2bb23
commit: 46cbdf967ada11b0286060488b61635fd6a2bb23
branch: main
author: Jelle Zijlstra <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2025-07-21T21:43:34-07:00
summary:

gh-135228: When @dataclass(slots=True) replaces a dataclass, make the original 
class collectible (#136893)

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 <[email protected]>

files:
A Misc/NEWS.d/next/Library/2025-07-20-16-56-55.gh-issue-135228.n_XIao.rst
M Lib/dataclasses.py
M Lib/test/test_dataclasses/__init__.py

diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py
index 83ea623dce6281..22b78bb2fbe6ed 100644
--- a/Lib/dataclasses.py
+++ b/Lib/dataclasses.py
@@ -1338,6 +1338,13 @@ def _add_slots(cls, is_frozen, weakref_slot, 
defined_fields):
                 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
 
 
@@ -1732,3 +1739,11 @@ def _replace(self, /, **changes):
     # 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()
diff --git a/Lib/test/test_dataclasses/__init__.py 
b/Lib/test/test_dataclasses/__init__.py
index e98a8f284cec9f..6bf5e5b3e5554b 100644
--- a/Lib/test/test_dataclasses/__init__.py
+++ b/Lib/test/test_dataclasses/__init__.py
@@ -3804,6 +3804,41 @@ class WithCorrectSuper(CorrectSuper):
         # 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):
diff --git 
a/Misc/NEWS.d/next/Library/2025-07-20-16-56-55.gh-issue-135228.n_XIao.rst 
b/Misc/NEWS.d/next/Library/2025-07-20-16-56-55.gh-issue-135228.n_XIao.rst
new file mode 100644
index 00000000000000..ee8962c6f46e75
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-07-20-16-56-55.gh-issue-135228.n_XIao.rst
@@ -0,0 +1,4 @@
+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`.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to