我想要的是一个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 = {}
我的问题是缓存部分似乎涉及很多开销(例如,用于递归函数)。使用以下函数作为示例,我可以使用非memoized版本调用fib(30)十次,而不是记忆版本。
def fib(n):
if n in (0, 1):
return n
return fib(n-1) + fib(n-2)
有人能建议更好的方法来编写这个装饰器吗? (或指向一个更好(即更快)的装饰器,做我想要的)。 我对保留方法签名或帮助内省工具“了解”有关装饰函数的任何内容不感兴趣。
感谢。
P.S。使用python 2.7
答案 0 :(得分:12)
您实际上并未缓存任何数据,因为每次设置新的缓存值都会覆盖前一个数据:
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 = {}
其他一些说明:
__prefix
;没有理由在这里,它只是丑化了代码。Memoized
实例提供自己的dict,并保留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中运行的版本,请参阅编辑历史记录。
答案 1 :(得分: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()