Caching class attributes in Python

后端 未结 9 1013
暖寄归人
暖寄归人 2020-11-30 22:05

I\'m writing a class in python and I have an attribute that will take a relatively long time to compute, so I only want to do it once. Also, it will not be

相关标签:
9条回答
  • 2020-11-30 22:10

    Python ≥ 3.8 @property and @functools.lru_cache have been combined into @cached_property.

    import functools
    class MyClass:
        @functools.cached_property
        def foo(self):
            print("long calculation here")
            return 21 * 2
    

    Python ≥ 3.2 < 3.8

    You should use both @property and @functools.lru_cache decorators:

    import functools
    class MyClass:
        @property
        @functools.lru_cache()
        def foo(self):
            print("long calculation here")
            return 21 * 2
    

    This answer has more detailed examples and also mentions a backport for previous Python versions.

    Python < 3.2

    The Python wiki has a cached property decorator (MIT licensed) that can be used like this:

    import random
    # the class containing the property must be a new-style class
    class MyClass(object):
       # create property whose value is cached for ten minutes
       @cached_property(ttl=600)
       def randint(self):
           # will only be evaluated every 10 min. at maximum.
           return random.randint(0, 100)
    

    Or any implementation mentioned in the others answers that fits your needs.
    Or the above mentioned backport.

    0 讨论(0)
  • 2020-11-30 22:15

    With Python 2, but not Python 3, here's what I do. This is about as efficient as you can get:

    class X:
        @property
        def foo(self):
            r = 33
            self.foo = r
            return r
    

    Explanation: Basically, I'm just overloading a property method with the computed value. So after the first time you access the property (for that instance), foo ceases to be a property and becomes an instance attribute. The advantage of this approach is that a cache hit is as cheap as possible because self.__dict__ is being used as the cache, and there is no instance overhead if the property is not used.

    This approach doesn't work with Python 3.

    0 讨论(0)
  • 2020-11-30 22:25

    The most simple way of doing this would probably be to just write a method (instead of using an attribute) that wraps around the attribute (getter method). On the first call, this methods calculates, saves and returns the value; later it just returns the saved value.

    0 讨论(0)
  • 2020-11-30 22:31

    I used to do this how gnibbler suggested, but I eventually got tired of the little housekeeping steps.

    So I built my own descriptor:

    class cached_property(object):
        """
        Descriptor (non-data) for building an attribute on-demand on first use.
        """
        def __init__(self, factory):
            """
            <factory> is called such: factory(instance) to build the attribute.
            """
            self._attr_name = factory.__name__
            self._factory = factory
    
        def __get__(self, instance, owner):
            # Build the attribute.
            attr = self._factory(instance)
    
            # Cache the value; hide ourselves.
            setattr(instance, self._attr_name, attr)
    
            return attr
    

    Here's how you'd use it:

    class Spam(object):
    
        @cached_property
        def eggs(self):
            print 'long calculation here'
            return 6*2
    
    s = Spam()
    s.eggs      # Calculates the value.
    s.eggs      # Uses cached value.
    
    0 讨论(0)
  • 2020-11-30 22:31

    Python 3.8 includes the functools.cached_property decorator.

    Transform a method of a class into a property whose value is computed once and then cached as a normal attribute for the life of the instance. Similar to property(), with the addition of caching. Useful for expensive computed properties of instances that are otherwise effectively immutable.

    This example is straight from the docs:

    from functools import cached_property
    
    class DataSet:
        def __init__(self, sequence_of_numbers):
            self._data = sequence_of_numbers
    
        @cached_property
        def stdev(self):
            return statistics.stdev(self._data)
    
        @cached_property
        def variance(self):
            return statistics.variance(self._data)
    

    The limitation being that the object with the property to be cached must have a __dict__ attribute that is a mutable mapping, ruling out classes with __slots__ unless __dict__ is defined in __slots__.

    0 讨论(0)
  • 2020-11-30 22:32

    The usual way would be to make the attribute a property and store the value the first time it is calculated

    import time
    
    class Foo(object):
        def __init__(self):
            self._bar = None
    
        @property
        def bar(self):
            if self._bar is None:
                print "starting long calculation"
                time.sleep(5)
                self._bar = 2*2
                print "finished long caclulation"
            return self._bar
    
    foo=Foo()
    print "Accessing foo.bar"
    print foo.bar
    print "Accessing foo.bar"
    print foo.bar
    
    0 讨论(0)
提交回复
热议问题