◐ Shell
clean mode source ↗

gh-90562: Update __class__ in function closures for dataclasses with slots=True by wookie184 · Pull Request #104038 · python/cpython

Opened as a draft as I haven't updated docs or added news yet.

This is similar to how attrs does it, which was mentioned in the issue. https://github.com/python-attrs/attrs/blob/7c44cfb3c196f547d351af44e411b43f65819714/src/attr/_make.py#L876-L897.

As well as attrs functionality, this also supports:

  • Functions decorated with decorators made with functools.wraps
  • Properties without getters

There are some cases that are not supported:

  • Functions decorated with decorators that don't use functools.wraps (or more precisely, don't have a __wrapped__ attribute which allows us to access the original function defined in the class).
  • Any other reason that the function is no longer in the class dictionary, any examples of that I can think of are pretty obscure though.

Note that since the __class__ cell is shared between functions, these unsupported methods will work if there is one occurence of a supported way of doing things. This is a bit of a gotcha, but I think if it's documented it should be acceptable.

I made an attempt at getting an idea of what the effect of this on performance will be on existing code

def make_dataclass():
    @dataclass(slots=True)
    class Foo:
        a: int
        b: int
        c: int
        d: int
        e: int
        f: int
        g: int

        def h(self): ...
        def i(self): ...
        def j(self): ...
        def k(self): ...
        def l(self): ...
        def m(self): ...
        def n(self): ...
        def o(self): ...
        def p(self): ...
        def q(self): ...

print(timeit.repeat(make_dataclass, number=1000, repeat=3))

Before: [0.9140953719997924, 0.9357239580003807, 0.9163554130000193]
After: [0.9437777250004729, 0.9911788059998798, 0.9630459829995743]
There is a difference, but I think it should be acceptable given it only affects dataclass creation.