Skip to content

3.14 regression: slot dataclasses classes leak original class #135228

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
Tinche opened this issue Jun 6, 2025 · 2 comments
Open

3.14 regression: slot dataclasses classes leak original class #135228

Tinche opened this issue Jun 6, 2025 · 2 comments
Assignees
Labels
3.14 bugs and security fixes 3.15 new features, bugs and security fixes stdlib Python modules in the Lib dir topic-dataclasses type-bug An unexpected behavior, bug, or error

Comments

@Tinche
Copy link
Contributor

Tinche commented Jun 6, 2025

Bug report

Bug description:

While trying to test cattrs on 3.14 I ran into this issue. Here's a simple reproducer that passes on 3.13, but doesn't on 3.14.

import gc
from dataclasses import dataclass


@dataclass(slots=True)
class B:
    b: int


gc.collect()

assert [
    o for o in gc.get_objects() if hasattr(o, "__name__") and o.__name__ == "B"
] == [B]

Originally I ran into this issue with slotted attrs classes but the core problem is the same. Since slotness cannot be added to a class, dataclasses and attrs classes create a class copy with slots instead. The old class used to hang around until a GC collection happened (guess there are some reference cycles, but I never investigated thoroughly).

On 3.14, a full GC collection does not clean up the original class, causing a leak. Apart from leaking, in case of subclassing the original class will remain in the list of the parent .__subclasses__(), causing an issue.

CPython versions tested on:

3.14

Operating systems tested on:

macOS

Linked PRs

@Tinche Tinche added the type-bug An unexpected behavior, bug, or error label Jun 6, 2025
@JelleZijlstra
Copy link
Member

I think this is because the annotate function for the class holds on to the original class namespace, and that namespace dict contains the descriptors B.__dict__ and B.__weakref__ that refer back to the original class. Not too sure how to fix that yet.

@JelleZijlstra JelleZijlstra self-assigned this Jun 7, 2025
@ZeroIntensity ZeroIntensity added 3.14 bugs and security fixes topic-dataclasses 3.15 new features, bugs and security fixes labels Jun 7, 2025
JelleZijlstra added a commit to JelleZijlstra/cpython that referenced this issue Jun 7, 2025
@JelleZijlstra
Copy link
Member

This seems hard to avoid, maybe we should just live with the change? The reason is that the annotation scope needs to hold a reference to the actual class dict (for reasons explained in https://jellezijlstra.github.io/pep695#class-scopes) and the class dict contains two descriptors, for __dict__ and __weakref__, that contain references back to the class object.

In Python 3.12 and 3.13 this behavior is already observable if you use a type alias or TypeVar bound in the dataclass, with one of these class definitions:

@dataclass(slots=True)
class B:
    def f[T: int](self, x: T) -> T: ...
    b: int

@dataclass(slots=True)
class B:
    type T = int
    b: int

Both of these fail the assertion in the post above.

I put up a change in #135230 that fixes this issue, but messing with the descriptors feels like it's probably going to break something else.

@picnixz picnixz added the stdlib Python modules in the Lib dir label Jun 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.14 bugs and security fixes 3.15 new features, bugs and security fixes stdlib Python modules in the Lib dir topic-dataclasses type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

4 participants