gh-135228: When @dataclass(slots=True) replaces a dataclass, make the original class collectible (take 2) (GH-137047)

Remove the `__dict__` and `__weakref__` descriptors from the original class when creating a dataclass from it.

An interesting hack, but more localized in scope than gh-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>
Co-authored-by: Petr Viktorin <encukou@gmail.com>
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
Jelle Zijlstra 2025-08-12 04:16:54 -07:00 committed by GitHub
parent 027cacb67c
commit 6859b95cff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 117 additions and 7 deletions

View file

@ -1283,6 +1283,10 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields):
if '__slots__' in cls.__dict__:
raise TypeError(f'{cls.__name__} already specifies __slots__')
# gh-102069: Remove existing __weakref__ descriptor.
# gh-135228: Make sure the original class can be garbage collected.
sys._clear_type_descriptors(cls)
# Create a new dict for our new class.
cls_dict = dict(cls.__dict__)
field_names = tuple(f.name for f in fields(cls))
@ -1300,12 +1304,6 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields):
# available in _MARKER.
cls_dict.pop(field_name, None)
# Remove __dict__ itself.
cls_dict.pop('__dict__', None)
# Clear existing `__weakref__` descriptor, it belongs to a previous type:
cls_dict.pop('__weakref__', None) # gh-102069
# And finally create the class.
qualname = getattr(cls, '__qualname__', None)
newcls = type(cls)(cls.__name__, cls.__bases__, cls_dict)