我一直在使用以下的memoizing装饰器(来自伟大的书籍Python算法:掌握Python语言中的基本算法...喜欢它,顺便说一句。)
def memo(func):
cache = {}
@ wraps(func)
def wrap(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrap
这个装饰器的问题是基于字典的缓存意味着我的所有参数都必须是可散列的。
是否有人有一个允许不可用的参数(例如词典)的实现(或对此的调整)?
我知道缺少哈希值意味着“这是否在缓存中?”变得非常重要,但我只是想我会问。
===编辑给出上下文===
我正在开发一个函数,它返回一个Parnas风格的“使用层次结构”给定模块的字典:依赖项。这是设置:
def uses_hierarchy(requirements):
"""
uses_hierarchy(requirements)
Arguments:
requirements - a dictionary of the form {mod: list of dependencies, }
Return value:
A dictionary of the form {level: list of mods, ...}
Assumptions:
- No cyclical requirements (e.g. if a requires b, b cannot require a).
- Any dependency not listed as a mod assumed to be level 0.
"""
levels = dict([(mod, _level(mod, requirements))
for mod in requirements.iterkeys()])
reversed = dict([(value, []) for value in levels.itervalues()])
for k, v in levels.iteritems():
reversed[v].append(k)
return reversed
def _level(mod, requirements):
if not requirements.has_key(mod):
return 0
dependencies = requirements[mod]
if not dependencies:
return 0
else:
return max([_level(dependency, requirements)
for dependency in dependencies]) + 1
那样:
>>> requirements = {'a': [],
... 'b': [],
... 'c': ['a'],
... 'd': ['a','b'],
... 'e': ['c','d'],
... 'f': ['e']
... }
>>> uses_hierarchy(requirements)
{0: ['a', 'b'], 1: ['c', 'd'], 2: ['e'], 3: ['f']}
_level是我想要记忆的功能,使这个设置更具可扩展性。在没有memoization的情况下实现,它会多次计算依赖关系的级别(例如,'a'在我上面的例子中计算8次)。
谢谢,
麦克
答案 0 :(得分:16)
Alex Martelli Python Cookbook中的示例演示如何使用cPickle创建一个memoize装饰器,用于获取可变参数的函数(原始版本):
import cPickle
class MemoizeMutable:
def __init__(self, fn):
self.fn = fn
self.memo = {}
def __call__(self, *args, **kwds):
import cPickle
str = cPickle.dumps(args, 1)+cPickle.dumps(kwds, 1)
if not self.memo.has_key(str):
print "miss" # DEBUG INFO
self.memo[str] = self.fn(*args, **kwds)
else:
print "hit" # DEBUG INFO
return self.memo[str]
这是link。
编辑:使用您提供的代码和此memoize装饰器:
_level = MemoizeMutable(_level)
equirements = {'a': [],
'b': [],
'c': ['a'],
'd': ['a','b'],
'e': ['c','d'],
'f': ['e']
}
print uses_hierarchy(equirements)
我能够重现这个:
miss
miss
hit
miss
miss
hit
miss
hit
hit
hit
miss
hit
{0: ['a', 'b'], 1: ['c', 'd'], 2: ['e'], 3: ['f']}
答案 1 :(得分:7)
从技术上讲,您可以将dict
(或list
或set
)转换为元组来解决此问题。例如:
key = tuple(the_dict.iteritems())
key = tuple(the_list)
key = tuple(sorted(the_set))
cache[key] = func( .. )
但我不会在memo
中执行此操作,我宁愿更改您要使用备忘录的功能 - 例如,他们不应接受dict
,而应接受(key, value)
对,而不是采取列表或集合,他们应该采取*args
。
答案 2 :(得分:2)
没有经过严格测试,但似乎有效:
from functools import wraps
def memo(func):
cache = []
argslist = []
@ wraps(func)
def wrap(*args):
try:
result = cache[argslist.index(args)]
print 'cache hit'
return result
except ValueError:
argslist.append(args)
cache.append(func(*args))
print 'cache miss'
return cache[-1]
return wrap
d1 = { 'a':3, 'b':42 }
d2 = { 'c':7, 'd':19 }
d3 = { 'e':34, 'f':67 }
@memo
def func(d):
return sum(d.values())
print func(d1)
# cache miss
# 45
print func(d2)
# cache miss
# 26
print func(d3)
# cache miss
# 101
print func(d2)
# cache hit
# 26
答案 3 :(得分:2)
由于没有人提及它,Python Wiki有一个装饰库,其中包含许多memoizing decorator patterns。
我的个人偏好是最后一个,它允许调用代码简单地将方法视为延迟评估的属性,而不是方法。但我更喜欢实施here。
class lazy_property(object):
'''Decorator: Enables the value of a property to be lazy-loaded.
From Mercurial's util.propertycache
Apply this decorator to a no-argument method of a class and you
will be able to access the result as a lazy-loaded class property.
The method becomes inaccessible, and the property isn't loaded
until the first time it's called. Repeated calls to the property
don't re-run the function.
This takes advantage of the override behavior of Descriptors -
__get__ is only called if an attribute with the same name does
not exist. By not setting __set__ this is a non-data descriptor,
and "If an instance's dictionary has an entry with the same name
as a non-data descriptor, the dictionary entry takes precedence."
- http://users.rcn.com/python/download/Descriptor.htm
To trigger a re-computation, 'del' the property - the value, not
this class, will be deleted, and the value will be restored upon
the next attempt to access the property.
'''
def __init__(self,func):
self.func = func
self.name = func.__name__
def __get__(self, obj, type=None):
result = self.func(obj)
setattr(obj, self.name, result)
return result
在上面链接的同一文件中,还有一个lazy_dict
装饰器,它允许您将函数视为具有延迟评估值的字典。
答案 4 :(得分:0)