Answers:
记忆有效地指基于方法输入记忆方法调用的结果(“记忆”→“备忘录”→要记忆),然后返回记忆的结果,而不是再次计算结果。您可以将其视为方法结果的缓存。有关更多详细信息,请参见第387页,Cormen等人的算法简介(3e)中的定义。
一个简单的示例,使用Python中的记忆来计算阶乘是这样的:
factorial_memo = {}
def factorial(k):
if k < 2: return 1
if k not in factorial_memo:
factorial_memo[k] = k * factorial(k-1)
return factorial_memo[k]
您可能会变得更加复杂,并将备注过程封装到一个类中:
class Memoize:
def __init__(self, f):
self.f = f
self.memo = {}
def __call__(self, *args):
if not args in self.memo:
self.memo[args] = self.f(*args)
#Warning: You may wish to do a deepcopy here if returning objects
return self.memo[args]
然后:
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
factorial = Memoize(factorial)
Python 2.4中添加了一个称为“ 装饰器 ”的功能,使您现在只需编写以下代码即可完成同一操作:
@Memoize
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
在Python的装饰图书馆有一个名为类似的装饰memoized
是不是稍微更强大的是Memoize
这里显示类。
factorial_memo
,因为factorial
内部def factorial
仍然调用旧的unmemoize factorial
。
if k not in factorial_memo:
,其阅读效果优于if not k in factorial_memo:
。
args
是一个元组。def some_function(*args)
使args为元组。
是Python 3.2的新功能functools.lru_cache
。默认情况下,它仅缓存最近使用的128个调用,但是您可以将设置为maxsize
,None
以指示缓存永不过期:
import functools
@functools.lru_cache(maxsize=None)
def fib(num):
if num < 2:
return num
else:
return fib(num-1) + fib(num-2)
此功能本身非常慢,请尝试fib(36)
,您将需要等待大约十秒钟。
添加lru_cache
注释可确保如果最近已为特定值调用了该函数,则该函数不会重新计算该值,而是使用缓存的先前结果。在这种情况下,它可以极大地提高速度,而代码不会因缓存的细节而混乱。
fib
调用时,需要先递归到基本情况,然后再进行存储。因此,您的行为与预期差不多。
备注保持了昂贵的计算结果并返回缓存的结果,而不是不断地重新计算它。
这是一个例子:
def doSomeExpensiveCalculation(self, input):
if input not in self.cache:
<do expensive calculation>
self.cache[input] = result
return self.cache[input]
有关备注的详细信息,请参见Wikipedia条目。
if input not in self.cache
和 self.cache[input]
(has_key
已过时,因为......在2.x系列的早期,如果不是2.0。 self.cache(index)
从来没有正确IIRC)
hasattr
对于想手工制作的人,不要忘了内置功能。这样,您可以将mem缓存保留在函数定义内(而不是全局)。
def fact(n):
if not hasattr(fact, 'mem'):
fact.mem = {1: 1}
if not n in fact.mem:
fact.mem[n] = n * fact(n - 1)
return fact.mem[n]
我发现这非常有用
def memoize(function):
from functools import wraps
memo = {}
@wraps(function)
def wrapper(*args):
if args in memo:
return memo[args]
else:
rv = function(*args)
memo[args] = rv
return rv
return wrapper
@memoize
def fibonacci(n):
if n < 2: return n
return fibonacci(n - 1) + fibonacci(n - 2)
fibonacci(25)
functools.wraps
。
memo
以便释放内存?
记忆基本上是保存过去使用递归算法完成的操作的结果,以便减少在以后需要相同计算时遍历递归树的需求。
参见http://scriptbucket.wordpress.com/2012/12/11/introduction-to-memoization/
Python中的斐波那契记忆示例:
fibcache = {}
def fib(num):
if num in fibcache:
return fibcache[num]
else:
fibcache[num] = num if num < 2 else fib(num-1) + fib(num-2)
return fibcache[num]
好吧,我应该首先回答第一部分:什么是记忆?
这只是以时间交换内存的一种方法。想想乘法表。
在Python中使用可变对象作为默认值通常被认为是不好的。但是,如果明智地使用它,则实际上可以实现memoization
。
这是一个改编自http://docs.python.org/2/faq/design.html#why-are-default-values-shared-between-objects的示例
使用dict
函数定义中的可变变量,可以缓存中间计算结果(例如,在计算factorial(10)
后进行计算时factorial(9)
,我们可以重用所有中间结果)
def factorial(n, _cache={1:1}):
try:
return _cache[n]
except IndexError:
_cache[n] = factorial(n-1)*n
return _cache[n]
这是一个可以使用列表或字典类型参数而无需抱怨的解决方案:
def memoize(fn):
"""returns a memoized version of any function that can be called
with the same list of arguments.
Usage: foo = memoize(foo)"""
def handle_item(x):
if isinstance(x, dict):
return make_tuple(sorted(x.items()))
elif hasattr(x, '__iter__'):
return make_tuple(x)
else:
return x
def make_tuple(L):
return tuple(handle_item(x) for x in L)
def foo(*args, **kwargs):
items_cache = make_tuple(sorted(kwargs.items()))
args_cache = make_tuple(args)
if (args_cache, items_cache) not in foo.past_calls:
foo.past_calls[(args_cache, items_cache)] = fn(*args,**kwargs)
return foo.past_calls[(args_cache, items_cache)]
foo.past_calls = {}
foo.__name__ = 'memoized_' + fn.__name__
return foo
请注意,通过在handle_item中作为特殊情况实现自己的哈希函数,可以自然地将此方法扩展到任何对象。例如,要使该方法适用于将集合作为输入参数的函数,可以将其添加到handle_item中:
if is_instance(x, set):
return make_tuple(sorted(list(x)))
list
的参数[1, 2, 3]
可能会错误地被认为是相同的不同的set
,值为参数{1, 2, 3}
。此外,集合像字典一样是无序的,因此它们也必须是sorted()
。另请注意,递归数据结构参数将导致无限循环。
list
s和set
s被“简化为”同一个事物,因此变得彼此难以区分。sets
恐怕您的最新更新中描述的添加对的支持的示例代码无法避免。这可以很容易地通过分别可以看到[1,2,3]
并{1,2,3}
作为参数传递给“memoize的” d测试功能,看它是否就是所谓的两倍,因为它应该是,还是不行。
list
s和dict
s 存在类似的问题,因为a 可能list
具有与调用make_tuple(sorted(x.items()))
字典所导致的完全相同的内容。这两种情况的简单解决方案是将type()
value的值包含在生成的元组中。我可以想到一种专门用于处理set
s的更简单的方法,但是并不能一概而论。
与位置参数和关键字参数一起使用的解决方案,与关键字args的传递顺序无关(使用inspect.getargspec):
import inspect
import functools
def memoize(fn):
cache = fn.cache = {}
@functools.wraps(fn)
def memoizer(*args, **kwargs):
kwargs.update(dict(zip(inspect.getargspec(fn).args, args)))
key = tuple(kwargs.get(k, None) for k in inspect.getargspec(fn).args)
if key not in cache:
cache[key] = fn(**kwargs)
return cache[key]
return memoizer
cache = {}
def fib(n):
if n <= 1:
return n
else:
if n not in cache:
cache[n] = fib(n-1) + fib(n-2)
return cache[n]
if n not in cache
。使用cache.keys
会在python 2中建立不必要的列表
只是想添加到已经提供的答案中,Python装饰器库具有一些简单但有用的实现,与相比,它们还可以记住“无法散列的类型” functools.lru_cache
。