Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can i write python decorator for caching?

I'm trying to write python decorator for memoize. I have few questions.

  1. How does @memoize translate to memoize class's call function?
  2. Why does init expect an argument.
  3. Where is the cache stored? Is it associated with each and every function or it's a global variable? i.e Will there be two cache objects if i use @memoize for multiple functions.

..

class memoize:
    def __init__(self):
        self.cache = {}

    def __call__(self, function):
        def wrapper(*args, **kwargs):
            key = str(function.__name__) + str(args) + str(kwargs)
            if key in cache:
                return cache[key]
            else:
                value = function(*args, **kwargs)
                cache[key] = value
                return value
        return wrapper

@memoize
def fib(n):
    if n in (0, 1):
        return 1
    else:
        return fib(n-1) + fib(n-2)

for i in range(0, 10):
    print(fib(i))

I'm getting compilation error.

Traceback (most recent call last):
  File "memoize.py", line 17, in <module>
    @memoize
TypeError: __init__() takes exactly 1 argument (2 given)
like image 617
user1159517 Avatar asked Jan 26 '15 06:01

user1159517


1 Answers

  1. You should remember that @decorator is just a syntactic sugar for func = decorator(func). So here comes a difference:

(1)

@decorator 
def func():
     ...

is same as

func = decorator(func)  # Just call of __init__
func(...)               # Call of decorator.__call__

but (2)

@decorator(some_param)
def func():
     ...

is similiar to

# Call of __init__ plus call of __call__
func = decorator(some_param)(func)  
# Call of closure returned by decorator.__call__
func(...)   
  1. You have implemented decorator accepting arguments for (2) syntax, but do not provide them when using them as in example (1). That is why __init__ complaining, it receives func as second argument.

  2. You should write self.cache in a wrapper closure, so wrapper would reference corresponding decorator object. Writing just cache will cause global variable search and thus will fail.

UPD: I changed your code to approach (1):

class memoize:
    def __init__(self, function):
        self.cache = {}
        self.function = function

    def __call__(self, *args, **kwargs):        
        key = str(args) + str(kwargs)
        if key in self.cache:
            return self.cache[key]

        value = self.function(*args, **kwargs)
        self.cache[key] = value
        return value

@memoize
def fib(n):
    if n in (0, 1):
        return 1
    else:
        return fib(n-1) + fib(n-2)

for i in range(0, 10):
    print(fib(i))

print(fib.cache)
like image 117
myaut Avatar answered Oct 01 '22 16:10

myaut