3

私が欲しいのは、次のようなメモ化デコレータです。

  • 引数とキーワード引数の両方でインスタンスメソッドをメモ化できます
  • 1回の呼び出しで(グローバルに)クリアできるキャッシュがあります(関数ごとのキャッシュを使用するこのキャッシュ:python resettable instance method memoization decorator
  • かなり効率的です

私が見た例を微調整して、次のように思いつきました。

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) を 10 回呼び出すことができます。

def fib(n):

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

このデコレータを書くためのより良い方法を提案できる人はいますか? (または、私が望むことを行うより良い(つまり、より高速な)デコレータを教えてください)。メソッドのシグネチャを保持したり、イントロスペクション ツールが装飾された関数について「認識」できるようにすることには興味がありません。

ありがとう。

PS Python 2.7の使用

4

2 に答える 2

13

新しいキャッシュ値を設定するたびに以前の値を上書きするため、実際にはデータをキャッシュしていません。

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 で動作するバージョンについては、編集履歴を参照してください。

于 2011-02-07T22:39:51.670 に答える
2

これは、大幅に高速化されたバージョンです。残念ながら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()
于 2011-02-07T22:32:47.740 に答える