常见的memoization配方(如this或these)使用dict
来存储缓存,因此要求函数参数可以清除。
我希望函数能够使用尽可能多的不同参数类型,当然包括dict
,set
,list
。实现这一目标的最佳方法是什么?
我正在考虑的一种方法是将所有不可扩展的参数包装到其可扩展的子类中(即,定义dict
的子类,定义其自己的__hash__
函数。)
或者,我正在考虑创建一个dict
的子类,它依赖于与hash
不同的哈希函数(定义一个递归工作的全局my_hash
函数并不难。容器),并使用此子类来存储缓存。但我认为没有一种简单的方法可以做到这一点。
编辑:
我想我会尝试the solution我建议用于python容器的一般散列。有了这个,我应该能够将(*args, **kwargs)
的元组包装到自动可以清除的类中,并使用常规的memoization。
答案 0 :(得分:0)
方法1 (拆分键和值)
这是基于字典只是压缩键和值的想法。 有了这个想法,我们可以创建类似字典来存储键(函数参数)和值(从函数中返回值)。
不确定它使用list.index
的速度有多慢。也许zip
ping会更快?
class memoize:
def __init__(self, func):
self.func = func
self.known_keys = []
self.known_values = []
def __call__(self, *args, **kwargs):
key = (args, kwargs)
if key in self.known_keys:
i = self.known_keys.index(key)
return self.known_values[i]
else:
value = self.func(*args, **kwargs)
self.known_keys.append(key)
self.known_values.append(value)
return value
有效!:
>>> @memoize
... def whatever(unhashable):
... print(*unhashable) # Just to know when called for this example
... return 12345
...
>>> whatever([1, 2, 3, 4])
1 2 3 4
12345
>>> whatever([1, 2, 3, 4])
12345
>>> whatever({"a": "b", "c": "d"})
a c
12345
>>> whatever({"a": "b", "c": "d"})
12345
方法2 (假哈希)
class memoize:
def __init__(self, func):
self.func = func
self.known = {}
def __call__(self, *args, **kwargs):
key = give_fake_hash((args, kwargs))
try:
return self.known[key]
except KeyError:
value = self.func(*args, **kwargs)
self.known[key] = value
return value
def give_fake_hash(obj):
cls = type(obj)
name = "Hashable" + cls.__name__
def fake_hash(self):
return hash(repr(self))
t = type(name, (cls, ), {"__hash__": fake_hash})
return t(obj)
方法2.5 (适用于dicts)
import operator
class memoize:
def __init__(self, func):
self.func = func
self.known = {}
def __call__(self, *args, **kwargs):
key = give_fake_hash((args, kwargs))
try:
return self.known[key]
except KeyError:
value = self.func(*args, **kwargs)
self.known[key] = value
return value
def fake_hash(self):
return hash(repr(self))
class HashableTuple(tuple):
__hash__ = fake_hash
class RereprDict(dict):
def __repr__(self):
try:
self._cached_repr
except AttributeError:
self._cached_repr = repr(sorted(self.items(), key=operator.itemgetter(0)))
finally:
return self._cached_repr
__hash__ = fake_hash
def fix_args(args):
for elem in args:
if isinstance(elem, dict):
elem = RereprDict(elem)
yield elem
def give_fake_hash(tup):
args, kwargs = tup
args = tuple(fix_args(args))
kwargs = RereprDict(kwargs)
return HashableTuple((args, kwargs))
答案 1 :(得分:0)
有一个原因是dict / list / set / etc。是不可以清洗的,它们是可变的。
这是他们不可变对应物存在的主要原因之一(frozendict / frozenset / tuple)。 (好吧,元组并不是一个完整的冻结列表,但实际上它可以达到目的)。
因此,出于您的目的,使用不可变的替代方案。
这里快速演示了为什么你不应该对可变对象进行哈希处理。请记住,散列需要a==b ==> hash(a)==hash(b)
。
@memoize
def f(x): ...
d1 = {'a':5}
d2 = {'a':99}
res1 = f(d1)
res2 = f(d2)
d1['a'] = 99
res3 = f(d1) # what should be returned? not well defined...