Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decorator for a class method that caches return value after first access

My problem, and why

I'm trying to write a decorator for a class method, @cachedproperty. I want it to behave so that when the method is first called, the method is replaced with its return value. I also want it to behave like @property so that it doesn't need to be explicitly called. Basically, it should be indistinguishable from @property except that it's faster, because it only calculates the value once and then stores it. My idea is that this would not slow down instantiation like defining it in __init__ would. That's why I want to do this.

What I tried

First, I tried to override the fget method of the property, but it's read-only.

Next, I figured I'd try to implement a decorator that does needs to be called the first time but then caches the values. This isn't my final goal of a property-type decorator that never needs to be called, but I thought this would be a simpler problem to tackle first. In other words, this is a not-working solution to a slightly simpler problem.

I tried:

def cachedproperty(func):     """ Used on methods to convert them to methods that replace themselves          with their return value once they are called. """     def cache(*args):         self = args[0] # Reference to the class who owns the method         funcname = inspect.stack()[0][3] # Name of the function, so that it can be overridden.         setattr(self, funcname, func()) # Replace the function with its value         return func() # Return the result of the function     return cache 

However, this doesn't seem work. I tested this with:

>>> class Test: ...     @cachedproperty ...     def test(self): ...             print "Execute" ...             return "Return" ...  >>> Test.test <unbound method Test.cache> >>> Test.test() 

but I get an error about how the class didn't pass itself to the method:

Traceback (most recent call last):   File "<stdin>", line 1, in <module> TypeError: unbound method cache() must be called with Test instance as first argument (got nothing instead) 

At this point, me and my limited knowledge of deep Python methods are very confused, and I have no idea where my code went wrong or how to fix it. (I've never tried to write a decorator before)

The question

How can I write a decorator that will return the result of calling a class method the first time it's accessed (like @property does), and be replaced with a cached value for all subsequent queries?

I hope this question isn't too confusing, I tried to explain it as well as I could.

like image 282
Luke Taylor Avatar asked Apr 18 '16 01:04

Luke Taylor


People also ask

What is a cache decorator?

cache is a decorator that helps in reducing function execution for the same inputs using the memoization technique. The function returns the same value as lru_cache(maxsize=None) , where the cache grows indefinitely without evicting old values.

What is the decorator used to denote a class method?

In Python, the @classmethod decorator is used to declare a method in the class as a class method that can be called using ClassName. MethodName() . The class method can also be called using an object of the class. The @classmethod is an alternative of the classmethod() function.

Does Python cache function result?

Memoization allows you to optimize a Python function by caching its output based on the parameters you supply to it. Once you memoize a function, it will only compute its output once for each set of parameters you call it with. Every call after the first will be quickly retrieved from a cache.

What does @cache do in Python?

Caching is one approach that, when used correctly, makes things much faster while decreasing the load on computing resources. Python's functools module comes with the @lru_cache decorator, which gives you the ability to cache the result of your functions using the Least Recently Used (LRU) strategy.


2 Answers

If you don't mind alternative solutions, I'd recommend lru_cache

for example

from functools import lru_cache class Test:     @property     @lru_cache(maxsize=None)     def calc(self):         print("Calculating")         return 1 

Expected output

In [2]: t = Test()  In [3]: t.calc Calculating Out[3]: 1  In [4]: t.calc Out[4]: 1 
like image 130
00500005 Avatar answered Oct 08 '22 15:10

00500005


First of all Test should be instantiated

test = Test() 

Second, there is no need for inspect cause we can get the property name from func.__name__ And third, we return property(cache) to make python to do all the magic.

def cachedproperty(func):     " Used on methods to convert them to methods that replace themselves\         with their return value once they are called. "      def cache(*args):         self = args[0] # Reference to the class who owns the method         funcname = func.__name__         ret_value = func(self)         setattr(self, funcname, ret_value) # Replace the function with its value         return ret_value # Return the result of the function      return property(cache)   class Test:     @cachedproperty     def test(self):             print "Execute"             return "Return"  >>> test = Test() >>> test.test Execute 'Return' >>> test.test 'Return' >>> 

"""

like image 22
robyschek Avatar answered Oct 08 '22 14:10

robyschek