Repository navigation
Add the @cached_property decorator #65344
Copy link
Copy link
Closed
Labels
3.8 (EOL)end of lifeend of lifestdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement
Description
| BPO | 21145 |
|---|---|
| Nosy | @rhettinger, @ncoghlan, @pitrou, @vstinner, @carljm, @merwok, @alex, @bitdancer, @serhiy-storchaka, @thedrow, @sir-sigurd |
| PRs | |
| Files |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee = None
closed_at = <Date 2018-08-30.14:04:19.713>
created_at = <Date 2014-04-03.10:28:52.512>
labels = ['3.8', 'type-feature', 'library']
title = 'Add the @cached_property decorator'
updated_at = <Date 2018-10-25.21:06:57.823>
user = 'https://github.com/thedrow'bugs.python.org fields:
activity = <Date 2018-10-25.21:06:57.823>
actor = 'vstinner'
assignee = 'none'
closed = True
closed_date = <Date 2018-08-30.14:04:19.713>
closer = 'ncoghlan'
components = ['Library (Lib)']
creation = <Date 2014-04-03.10:28:52.512>
creator = 'Omer.Katz'
dependencies = []
files = ['45431', '45450', '45452']
hgrepos = []
issue_num = 21145
keywords = ['patch']
message_count = 45.0
messages = ['215439', '215488', '215564', '215654', '215655', '218750', '218760', '218761', '218762', '260308', '260312', '260319', '260321', '260322', '260323', '260324', '260326', '280534', '280541', '280542', '280547', '280597', '280601', '280602', '280608', '280609', '280610', '280615', '280616', '280655', '280657', '316535', '316643', '316745', '316760', '316851', '317096', '317097', '317100', '317101', '317103', '324212', '324383', '324386', '328493']
nosy_count = 13.0
nosy_names = ['rhettinger', 'ncoghlan', 'pitrou', 'vstinner', 'carljm', 'pydanny', 'eric.araujo', 'alex', 'r.david.murray', 'serhiy.storchaka', 'madison.may', 'Omer.Katz', 'sir-sigurd']
pr_nums = ['6982']
priority = 'normal'
resolution = 'fixed'
stage = 'resolved'
status = 'closed'
superseder = None
type = 'enhancement'
url = 'https://bugs.python.org/issue21145'
versions = ['Python 3.8']Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.8 (EOL)end of lifeend of lifestdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement
Activity
thedrow commentedon Apr 3, 2014
cached properties are widely used in various prominent Python projects such as Django and pip (and many more).
They are not very complicated and there are proven implementation out there. Unfortunately there are way too many of them. This situation leads me to suggest adding it to the builtins.
Possible benefits:
MadisonMay commentedon Apr 4, 2014
There's currently an example of a cached property decorator implementation in the wiki, although it doesn't leverage functools:
https://wiki.python.org/moin/PythonDecoratorLibrary#Cached_Properties
merwok commentedon Apr 4, 2014
It could make sense to add clean, working recipes to e.g. the functools documentation. The cached_property in the wiki uses a TTL, other like Pyramid’s reify decorator make properties that ensure the fget function is called only once per instance, and there may be subtly different variants out there. I don’t know if there’s a universally useful variant that should be added to the sdlib right now. (I don’t think a C implementation is needed.)
On a related note, the Python docs about desciptors may be missing entry-level explanations, as described here: http://me.veekun.com/blog/2012/05/23/python-faq-descriptors/
thedrow commentedon Apr 6, 2014
The default implementation should simple:
Once the property is accessed for the first time calculate the result and
never calculate again.
It's what both Django & pip uses.
You can add an optional TTL.
There aren't any other features I can come up with that are common enough.
If needed, one can inherit from cached_property and do whatever is needed.
Eric, Why don't you think a C implementation is needed? It's a simple
operation for sure but it is meant to increase runtime efficiency.
2014-04-05 1:11 GMT+04:00 Éric Araujo <report@bugs.python.org>:
thedrow commentedon Apr 6, 2014
I just checked and werkzeug uses the same implementation as Django & pip.
2014-04-06 14:31 GMT+04:00 Omer Katz <report@bugs.python.org>:
pydanny commentedon May 18, 2014
For what it's worth, I just released cached-property on PyPI and someone suggested I join the discussion here.
Package: https://pypi.python.org/pypi/cached-property
Repo: https://github.com/pydanny/cached-property
Notes:
pitrou commentedon May 18, 2014
Does this work for you?
alex commentedon May 18, 2014
Will that implementation cause a memory leak? Won't the lru_cache have a dict mapping {self: result}, meaning that
selfwill live forever, instead of storing result in self.dict, where the lifetimes are correct.pitrou commentedon May 18, 2014
Oh, you're right. Sorry for the noise.
thedrow commentedon Feb 15, 2016
Can we make this happen for 3.6?
serhiy-storchaka commentedon Feb 15, 2016
May be. If somebody provide a patch.
vstinner commentedon Feb 15, 2016
I like the idea of an helper to build a property on-demand, but I dislike the TTL idea, it seems too specific.
If you need TTL, implement your own decorator, or use a regular property and implement your own logic there.
thedrow commentedon Feb 15, 2016
Most implementations these days support TTL because they require it.
The whole point is to remove the need to reimplement such basic
functionality over and over.
בתאריך יום ב׳, 15 בפבר׳ 2016 ב-18:33 מאת STINNER Victor <
report@bugs.python.org>:
pitrou commentedon Feb 15, 2016
The TTL idea is completely outlandish in a general-purpose library. Let's keep things simple and not try to build a kitchen-sink decorator.
22 remaining items
carljm commentedon May 16, 2018
This is already supported by the simple implementation in the patch, it's spelled
del obj.the_cached_property.This is easy to do with the current implementation, you can replace the cached-property descriptor on the class with
mock.patch.The patch attached here is already thread-safe and will be consistent between threads.
If you
sin the Python debugger on a line where a property or cached property is accessed, you will step into the decorated method. I've done this often, so I'm not sure what the issue would be here.This would be a totally different descriptor that doesn't share much implementation with the one proposed here, so I don't see how providing the common version inhibits anyone from writing something different they need for their case.
It's simple once you understand what it does, but it's quite subtle in the way it relies on priority order of instance-dict attributes vs non-data descriptors.
My experience over the past decade is different from yours; I've found that the simple
cached_propertyproposed here is widely and frequently useful (basically it should be the preferred approach anytime an object which is intended to be immutable after construction has some calculated properties based on its other attributes), and additional complexity is rarely if ever needed. I think the wide usage of the version proposed here (without extensions) in code in the wild bears this out. Likely a main reason there hasn't been a stronger push to include this in the standard library sooner is that so many people are just using it fromdjango.utils.functional.cached_propertytoday.pitrou commentedon May 16, 2018
I agree this would be a useful addition to the stdlib. The code might seem reasonably short, but implementing new descriptors is an advanced topic (I'd rather avoid it myself).
carljm commentedon May 19, 2018
Sent a PR with the patch.
Nick, I tried your
__set_name__proposal to get an earlier error in case of an object with slots, but it has the downside that Python seems to always raise a new chained exception if__set_name__raises any exception. So instead of getting a clear error, you get an opaque one about "error raised when calling set_name on...", and you have to scroll up to see the real error message. I felt that this was too much usability regression and not worth the benefit of raising the error sooner. Let me know if you feel otherwise.ncoghlan commentedon May 19, 2018
I think it would make sense to remove the exception wrapping from the __set_name__ calls - I don't think we're improving the ease of understanding the tracebacks by converting everything to a generic RuntimeError, and we're hurting the UX of descriptor validation cases like this one.
[1] https://github.com/python/cpython/blob/master/Objects/typeobject.c#L7263
ncoghlan commentedon May 19, 2018
I filed https://bugs.python.org/issue33576 to cover removing the exception wrapping from __set_name__ errors.
carljm commentedon May 19, 2018
Makes sense to me. Sounds like a separate issue and PR; I filed bpo-33577 and will work on a patch.
carljm commentedon May 19, 2018
Oops, never mind; closed mine as dupe.
ncoghlan commentedon Aug 28, 2018
New changeset d658dea by Nick Coghlan (Carl Meyer) in branch 'master':
bpo-21145: Add cached_property decorator in functools (bpo-6982)
d658dea
ncoghlan commentedon Aug 30, 2018
This has now been merged.
Thanks for the multiple iterations on the implementation Carl, and thanks for the original proposal, Omer!
carljm commentedon Aug 30, 2018
Thanks everyone for the thoughtful and careful reviews! Patch is much improved from where it started. And thanks Nick for merging.
vstinner commentedon Oct 25, 2018
FYI there is discussion in bpo-34995 about the usage of @cached_property with abstract methods.