Skip to content

Add the @cached_property decorator #65344

Closed

Description

@thedrow
mannequin
BPO 21145
Nosy @rhettinger, @ncoghlan, @pitrou, @vstinner, @carljm, @merwok, @alex, @bitdancer, @serhiy-storchaka, @thedrow, @sir-sigurd
PRs
  • bpo-21145: Add cached_property decorator in functools #6982
  • Files
  • cached_property.diff: diff to add cached_property decorator, with tests and docs
  • cached_property.diff: same as previous, with doc note and better error message for object with slots
  • cached_property.diff: same as previous, but thread-safe
  • 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']

    Activity

    1. thedrow commented on Apr 3, 2014

      thedrowmannequin
      MannequinAuthor

      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:

      • Implementation in C can improve caching performance
      • Standardized implementation that can be used anywhere
    2. MadisonMay commented on Apr 4, 2014

      MadisonMaymannequin
      Mannequin

      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

    3. merwok commented on Apr 4, 2014

      @merwok
      Member

      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/

    4. thedrow commented on Apr 6, 2014

      thedrowmannequin
      MannequinAuthor

      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>:

      Éric Araujo added the comment:

      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/

      ----------
      nosy: +eric.araujo, rhettinger


      Python tracker <report@bugs.python.org>
      <http://bugs.python.org/issue21145\>


    5. thedrow commented on Apr 6, 2014

      thedrowmannequin
      MannequinAuthor

      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>:

      Omer Katz added the comment:

      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>:

      >
      > Éric Araujo added the comment:
      >
      > 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/
      >
      > ----------
      > nosy: +eric.araujo, rhettinger
      >
      > _______________________________________
      > Python tracker <report@bugs.python.org>
      > <http://bugs.python.org/issue21145\>
      > _______________________________________
      >

      ----------


      Python tracker <report@bugs.python.org>
      <http://bugs.python.org/issue21145\>


    6. pydanny commented on May 18, 2014

      pydannymannequin
      Mannequin

      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:

      • 92% test coverage, albeit with a simple, arguably naive test.
      • I plan to increase the coverage to 100%.
      • I'm not entirely sure where to give credit for the original implementation. Right now I think it's Bottle, but I'm not certain. See Credit where it's due pydanny/cached-property#1
    7. pitrou commented on May 18, 2014

      @pitrou
      Member

      Does this work for you?

      >>> class X:
      ...   @property
      ...   @functools.lru_cache(None)
      ...   def get_x(self):
      ...     print("computing")
      ...     return 5
      ... 
      >>> x = X()
      >>> x.get_x
      computing
      5
      >>> x.get_x
      5
    8. alex commented on May 18, 2014

      @alex
      Member

      Will that implementation cause a memory leak? Won't the lru_cache have a dict mapping {self: result}, meaning that self will live forever, instead of storing result in self.dict, where the lifetimes are correct.

    9. pitrou commented on May 18, 2014

      @pitrou
      Member

      Will that implementation cause a memory leak? Won't the lru_cache have
      a dict mapping {self: result}, meaning that self will live forever,
      instead of storing result in self.dict, where the lifetimes are
      correct.

      Oh, you're right. Sorry for the noise.

    10. thedrow commented on Feb 15, 2016

      thedrowmannequin
      MannequinAuthor

      Can we make this happen for 3.6?

    11. serhiy-storchaka commented on Feb 15, 2016

      @serhiy-storchaka
      Member

      May be. If somebody provide a patch.

    12. vstinner commented on Feb 15, 2016

      @vstinner
      Member

      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.

    13. thedrow commented on Feb 15, 2016

      thedrowmannequin
      MannequinAuthor

      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‬‏>:‬

      STINNER Victor added the comment:

      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.

      ----------


      Python tracker <report@bugs.python.org>
      <http://bugs.python.org/issue21145\>


    14. pitrou commented on Feb 15, 2016

      @pitrou
      Member

      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.

    15. 22 remaining items

    16. carljm commented on May 16, 2018

      @carljm
      Member

      a way to invalidate or clear the cache

      This is already supported by the simple implementation in the patch, it's spelled del obj.the_cached_property.

      mock patching the underlying function for testing

      This is easy to do with the current implementation, you can replace the cached-property descriptor on the class with mock.patch.

      consistency between multiple cached properties cached in different threads

      The patch attached here is already thread-safe and will be consistent between threads.

      inability to run the method through a debugger

      If you s in 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.

      moving the cache valued from an instance variables to an external weakref dictionary

      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.

      The basic recipe is simple so there isn't much of a value add by putting this in the standard library.

      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_property proposed 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 from django.utils.functional.cached_property today.

    17. pitrou commented on May 16, 2018

      @pitrou
      Member

      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).

    18. carljm commented on May 19, 2018

      @carljm
      Member

      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.

    19. ncoghlan commented on May 19, 2018

      @ncoghlan
      Contributor

      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

    20. ncoghlan commented on May 19, 2018

      @ncoghlan
      Contributor

      I filed https://bugs.python.org/issue33576 to cover removing the exception wrapping from __set_name__ errors.

    21. carljm commented on May 19, 2018

      @carljm
      Member

      Makes sense to me. Sounds like a separate issue and PR; I filed bpo-33577 and will work on a patch.

    22. carljm commented on May 19, 2018

      @carljm
      Member

      Oops, never mind; closed mine as dupe.

    23. ncoghlan commented on Aug 28, 2018

      @ncoghlan
      Contributor

      New changeset d658dea by Nick Coghlan (Carl Meyer) in branch 'master':
      bpo-21145: Add cached_property decorator in functools (bpo-6982)
      d658dea

    24. ncoghlan commented on Aug 30, 2018

      @ncoghlan
      Contributor

      This has now been merged.

      Thanks for the multiple iterations on the implementation Carl, and thanks for the original proposal, Omer!

    25. carljm commented on Aug 30, 2018

      @carljm
      Member

      Thanks everyone for the thoughtful and careful reviews! Patch is much improved from where it started. And thanks Nick for merging.

    26. vstinner commented on Oct 25, 2018

      @vstinner
      Member

      FYI there is discussion in bpo-34995 about the usage of @cached_property with abstract methods.

    27. transferred this issue fromon Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      3.8 (EOL)end of lifestdlibStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancement

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions