【问题标题】:What can be done to speed up this memoization decorator?可以做些什么来加速这个 memoization 装饰器?
【发布时间】:2011-06-23 01:49:15
【问题描述】:

我想要的是一个 memoization 装饰器:

我已经调整了我看到的一个示例并提出了以下内容:

import functools

class Memoized(object):
  """Decorator that caches a function's return value each time it is called.
  If called later with the same arguments, the cached value is returned, and
  not re-evaluated.
  """

  __cache = {}

  def __init__(self, func):
    self.func = func
    self.key = (func.__module__, func.__name__)

  def __call__(self, *args):
    try:
      return Memoized.__cache[self.key][args]
    except KeyError:
      value = self.func(*args)
      Memoized.__cache[self.key] = {args : value}
      return value
    except TypeError:
      # uncachable -- for instance, passing a list as an argument.
      # Better to not cache than to blow up entirely.
      return self.func(*args)

  def __get__(self, obj, objtype):
    """Support instance methods."""
    return functools.partial(self.__call__, obj)

  @staticmethod
  def reset():
    Memoized.__cache = {}

我的问题是缓存部分似乎涉及很多开销(例如,对于递归函数)。以下面的函数为例,我可以在非记忆版本比记忆版本更短的时间内调用 fib(30) 十次。

def fib(n):

   if n in (0, 1):
      return n
   return fib(n-1) + fib(n-2)

谁能推荐一个更好的方法来编写这个装饰器? (或者给我一个更好(即更快)的装饰器来做我想要的)。 我对保留方法签名或帮助自省工具“了解”装饰函数的任何内容不感兴趣。

谢谢。

附:使用python 2.7

【问题讨论】:

    标签: python memoization


    【解决方案1】:

    这是一个明显更快的版本。不幸的是,reset 不能再完全清除缓存,因为所有实例都在存储它们自己的对每个函数字典的引用的本地副本。虽然你可以让它工作:

    import functools
    
    class Memoized(object):
      """Decorator that caches a function's return value each time it is called.
      If called later with the same arguments, the cached value is returned, and
      not re-evaluated.
      """
    
      __cache = {}
    
      def __init__(self, func):
        self.func = func
        key = (func.__module__, func.__name__)
        if key not in self.__cache:
          self.__cache[key] = {}
        self.mycache = self.__cache[key]
    
      def __call__(self, *args):
        try:
          return self.mycache[args]
        except KeyError:
          value = self.func(*args)
          self.mycache[args] = value
          return value
        except TypeError:
          # uncachable -- for instance, passing a list as an argument.
          # Better to not cache than to blow up entirely.
          return self.func(*args)
    
      def __get__(self, obj, objtype):
        """Support instance methods."""
        return functools.partial(self.__call__, obj)
    
      @classmethod
      def reset(cls):
        for v in cls.__cache.itervalues():
          v.clear()
    

    【讨论】:

    • reset 需要是@classmethod,然后是reset(cls),然后是cls.__cache.itervalues()
    • @Nickpick - 很明显,这么多年前我急于写它,我没有完全测试它。
    【解决方案2】:

    您实际上并没有缓存任何数据,因为每次设置新的缓存值时都会覆盖以前的值:

    Memoized.__cache[self.key] = {args : value}
    

    例如。

    import functools
    
    class Memoized(object):
        """Decorator that caches a function's return value each time it is called.
        If called later with the same arguments, the cached value is returned, and
        not re-evaluated.
        """
    
        cache = {}
    
        def __init__(self, func):
            self.func = func
            self.key = (func.__module__, func.__name__)
            self.cache[self.key] = {}
    
        def __call__(self, *args):
          try:
              return Memoized.cache[self.key][args]
          except KeyError:
              value = self.func(*args)
              Memoized.cache[self.key][args] = value
              return value
          except TypeError:
              # uncachable -- for instance, passing a list as an argument.
              # Better to not cache than to blow up entirely.
              return self.func(*args)
    
        def __get__(self, obj, objtype):
            """Support instance methods."""
            return functools.partial(self.__call__, obj)
    
        @staticmethod
        def reset():
            Memoized.cache = {}
    
    • fib(30) 没有缓存:2.86742401123
    • 带有缓存的fib(30):0.000198125839233

    其他一些注意事项:

    • 不要使用__prefix;没有理由在这里,它只会丑化代码。
    • 不要使用单一的、整体的、类属性字典,而是为Memoized 的每个实例提供自己的字典,并保留Memoized 对象的注册表。这改进了封装性,并消除了依赖模块和函数名称的奇怪之处。

    .

    import functools
    import weakref
    
    class Memoized(object):
        """Decorator that caches a function's return value each time it is called.
        If called later with the same arguments, the cached value is returned, and
        not re-evaluated.
    
        >>> counter = 0
        >>> @Memoized
        ... def count():
        ...     global counter
        ...     counter += 1
        ...     return counter
    
        >>> counter = 0
        >>> Memoized.reset()
        >>> count()
        1
        >>> count()
        1
        >>> Memoized.reset()
        >>> count()
        2
    
        >>> class test(object):
        ...     @Memoized
        ...     def func(self):
        ...         global counter
        ...         counter += 1
        ...         return counter
        >>> testobject = test()
        >>> counter = 0
        >>> testobject.func()
        1
        >>> testobject.func()
        1
        >>> Memoized.reset()
        >>> testobject.func()
        2
        """
    
        caches = weakref.WeakSet()
    
        def __init__(self, func):
            self.func = func
            self.cache = {}
            Memoized.caches.add(self)
    
        def __call__(self, *args):
          try:
              return self.cache[args]
          except KeyError:
              value = self.func(*args)
              self.cache[args] = value
              return value
          except TypeError:
              # uncachable -- for instance, passing a list as an argument.
              # Better to not cache than to blow up entirely.
              return self.func(*args)
    
        def __get__(self, obj, objtype):
            """Support instance methods."""
            return functools.partial(self.__call__, obj)
    
        @staticmethod
        def reset():
            for memo in Memoized.caches:
                memo.cache = {}
    
    if __name__ == '__main__':
        import doctest
        doctest.testmod()
    

    编辑:添加测试,并使用weakref.WeakSet。请注意,WeakSet 仅在 2.7(OP 正在使用)中可用;对于适用于 2.6 的版本,请参阅编辑历史记录。

    【讨论】:

    • 哦。打扰了,你是对的。我应该注意到这一点。 :-) 我的版本仍然会更快,但不会快很多。
    • 这并不能真正解决性能问题。 OP 的版本确实缓存了一个项目,他通过使用相同的参数重复调用该函数来测试它,即使没有此更改也会被缓存。
    • @Glenn:谢谢!难怪我的版本这么慢!重置有一个小问题(我确定我的版本中也存在这个问题)。 "Memoized.cache" 递归调用 Memoized.__call__ 直到它得到一个 KeyError: ('main', 'fib')。
    • @Andrew:不,他在调用 recursive 函数;这确实解决了这个问题。 @Gerrat:在 2.6 中这不会发生在我身上。也许这是 2.7 中的新功能;如果是这样,我会将其归类为向后兼容的脑损伤(不幸的是,Python 以它而闻名)。
    • @Andrew:我还不确定为什么它更快(我认为我的版本正在缓存一些东西);但是对于相同的 fib 函数,Glenn 的版本(以及 Omnifarious 的版本)确实要快得多(现在几乎没有任何开销)。
    猜你喜欢
    • 1970-01-01
    • 2016-11-25
    • 1970-01-01
    • 2011-01-29
    • 2021-05-10
    • 1970-01-01
    • 2013-01-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多