Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Replace cached_property on slotted classes with a new descriptor instead of writing__getattr__#1488

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
DavidCEllis wants to merge22 commits intopython-attrs:main
base:main
Choose a base branch
Loading
fromDavidCEllis:descriptor-cached-property

Conversation

@DavidCEllis
Copy link
Contributor

@DavidCEllisDavidCEllis commentedDec 6, 2025
edited
Loading

Summary

This PR reimplementscached_property in slotted classes as a slot wrapping descriptor replacing the current generated__getattr__ implementation.

Accessing the attributes will probably be slightly slower due to going through this descriptor but if the performance is reasonable this willclose#1288#1325 and#1333.

This idea came up again as part of discussion onhttps://discuss.python.org/t/extensible-cached-property/105188/25. I hadn't realised it would work, but it looks like there was a partial attempt to implement it for attrs before in#1357

I've implemented it for my own attrs/dataclasses-likehere (the _SlottedCachedProperty code is mostly identical).

As I saw there were outstanding issues for it and the code is fairly similar I thought you might also find it useful. (Passing all of your existing tests also gave me some confidence that it does work correctly).


Changes:

  • Adds a_SlottedCachedProperty descriptor class that replaces the slot descriptors after attrs has rebuilt the class.
    • This is very similar tocached_property itself, other than reading/writing/deleting a wrapped slot rather than dict entry.
  • Removes the customised__getattr__ function you were making previously.
    • There are probably some redundant tests that were checking the__getattr__ behaviour that are now just checking__getattr__ works in Python.

Outstanding issues:

This was already the case for the__getattr__ implementation but I'm now aware of one difference in behaviour this hasn't yet resolved as it would require changing the logic of when you create a new cached property. There's currently a test marked as xfail for this.

importattrsimportfunctoolsforslottedin [False,True]:@attrs.define(slots=slotted)classParent:name:str="Alice"@attrs.define(slots=slotted)classChild(Parent):@functools.cached_propertydefname(self):return"Bob"# This isn't to imply that this is good# just that it's consistentp=Parent()c=Child()assertp.name=="Alice"assertc.name=="Alice"delc.nameassertc.name=="Bob"# True without slots, errors under slots.

This is because the cached_property replacement checkattrs does skips field names that already exist, soname is skipped in the check and doesn't get replaced. The descriptor would behave like this, but it's never placed.

Pull Request Check List

  • Donot open pull requests from yourmain branch –use a separate branch!
    • There's a ton of footguns waiting if you don't heed this warning. You can still go back to your project, create a branch from your main branch, push it, and open the pull request from the new branch.
    • This is not a pre-requisite for your pull request to be accepted, butyou have been warned.
  • Addedtests for changed code.
    Our CI fails if coverage is not 100%.
  • Changes or additions to public APIs are reflected in our type stubs (files ending in.pyi).
    • ...and used in the stub test filetyping-examples/baseline.py or, if necessary,typing-examples/mypy.py.
    • If they've been added toattr/__init__.pyi, they'vealso been re-imported inattrs/__init__.pyi.
  • Updateddocumentation for changed code.
    • New functions/classes have to be added todocs/api.rst by hand.
    • Changes to the signatures of@attr.s() and@attrs.define() have to be added by hand too.
    • Changed/added classes/methods/functions have appropriateversionadded,versionchanged, ordeprecateddirectives.
      The next version is the second number in the current release + 1.
      The first number represents the current year.
      So if the current version on PyPI is 22.2.0, the next version is gonna be 22.3.0.
      If the next version is the first in the new year, it'll be 23.1.0.
      • If something changed that affects bothattrs.define() andattr.s(), you have to add version directives to both.
  • Documentation in.rst and.md files is written usingsemantic newlines.
  • Changes (and possible deprecations) have news fragments inchangelog.d.
  • Consider grantingpush permissions to the PR branch, so maintainers can fix minor issues themselves without pestering you.

DavidCEllisand others added17 commitsDecember 5, 2025 21:02
The descriptor now carries the annotations or annotate function from the wrapped function.
@codspeed-hq
Copy link

codspeed-hqbot commentedDec 8, 2025
edited
Loading

CodSpeed Performance Report

Merging#1488 willdegrade performances by 71.97%

ComparingDavidCEllis:descriptor-cached-property (67a1858) withmain (460f019)

Summary

⚡ 1 improvement
❌ 1 regression
✅ 13 untouched

⚠️Please fix the performance issues oracknowledge them on CodSpeed.

Benchmarks breakdown

BenchmarkBASEHEADChange
test_repeated_access254.5 µs908.2 µs-71.97%
test_create_cached_property_class1.8 s1.1 s+53.72%

@DavidCEllis
Copy link
ContributorAuthor

Oof, that's worse than I thought. I have one idea that should make it slightly better, but probably still significantly worse than the direct access it has on main.

@DavidCEllis
Copy link
ContributorAuthor

Ah, it would have needed to be on main to see the difference. I haven't used codspeed before, sorry for the noise there. Locally it seemed to be significantly faster to construct classes at least. Probably less important than attribute access, but worth noting it's not uniformly worse performance-wise.

@hynek
Copy link
Member

would you like me to transplant the benchmark to main? i'm a bit busy (just came back from vacation with the obvious results), but i'm open to mechanical work ;)

@DavidCEllis
Copy link
ContributorAuthor

Sure, that would be great thanks. No rush if you're already swamped.

I tried a few other things to see if they affected performance but it seems it's entirely linked with wrapping the slot in another descriptor.

The only other thing I've tried so far that improved performance at all was replacing the new descriptor with a regularproperty that handles the caching. I don't like that as much as it makes recovering the slot/function more awkward (as you can't add attributes to a property) and it was only a little faster than the custom descriptor.

@hynek
Copy link
Member

done!

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

__getattr__ in child class gets called when mixed withcached_property

2 participants

@DavidCEllis@hynek

[8]ページ先頭

©2009-2025 Movatter.jp