我怎么能写python装饰器进行缓存?

时间:2015-01-26 06:30:31

标签: python caching decorator python-decorators

我正在尝试为memoize编写python decorator。 我几乎没有问题。

  1. @memoize如何翻译成memoize类的调用函数?
  2. 为什么 init 期望参数。
  3. 缓存存储在哪里?它与每个函数相关联,还是全局变量?即如果我使用@memoize,会有两个缓存对象 多功能。
  4. ...

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

    我收到了编译错误。

    Traceback (most recent call last):
      File "memoize.py", line 17, in <module>
        @memoize
    TypeError: __init__() takes exactly 1 argument (2 given)
    

2 个答案:

答案 0 :(得分:5)

  1. 您应该记住,@decorator只是func = decorator(func)的语法糖。所以这里有所不同:
  2. (1)

    @decorator 
    def func():
         ...
    

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

    但是(2)

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

    类似
    # Call of __init__ plus call of __call__
    func = decorator(some_param)(func)  
    # Call of closure returned by decorator.__call__
    func(...)   
    
    1. 您已经为(2)语法实现了装饰器接受参数,但在使用它们时不提供它们,如示例(1)所示。这就是__init__抱怨的原因,它接收func作为第二个参数。

    2. 您应该在self.cache闭包中写wrapper,因此wrapper会引用相应的decorator对象。仅写cache将导致全局变量搜索,因此将失败。

    3. UPD :我将您的代码更改为接近(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)
      

答案 1 :(得分:0)

This is a custom cache我已经满足了我的特定需求,并提供了一些额外的功能,例如清除缓存和更新密钥等等。

import datetime


class CustomCache:
    def __init__(self, function, max_size=100):
        """Constructor"""
        self.function = function
        self.cache = {}
        self.max_cache_size = max_size

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

        value = self.function(*args, **kwargs)
        self.update(key, value)
        return value

    def __contains__(self, key):
        """
        Returns True or False depending on whether or not the key is in the cache
        """
        return key in self.cache

    def update(self, key, value):
        """
        Update the cache dictionary and optionally remove the oldest item
        """

        if key not in self.cache and len(self.cache) >= self.max_cache_size:
            self.remove_oldest()
        self.cache[key] = {'date_accessed': datetime.datetime.now(), 'value': value}

    def update_key(self, value, *args, **kwargs):
        """
        Update specific key in cache just in case
        """
        key = str(args) + str(kwargs)
        if key in self.cache:
            self.cache[key] = {'date_accessed': datetime.datetime.now(), 'value': value}

    def remove_oldest(self):
        """
        Remove the entry that has the oldest accessed date
        """

        oldest_entry = None
        for key in self.cache:
            if oldest_entry is None:
                oldest_entry = key
            elif self.cache[key]['date_accessed'] < self.cache[oldest_entry]['date_accessed']:
                oldest_entry = key
        self.cache.pop(oldest_entry)

    def cache_clear(self):
        """
        Clear entire cache 
        """
        self.cache = {}

    @property
    def size(self):
        """
        Return the size of the cache
        """

        return len(self.cache)
相关问题