允许不可删除参数的记忆配方

时间:2015-04-13 01:18:49

标签: python python-3.x hash memoization

常见的memoization配方(如thisthese)使用dict来存储缓存,因此要求函数参数可以清除。

我希望函数能够使用尽可能多的不同参数类型,当然包括dictsetlist。实现这一目标的最佳方法是什么?

我正在考虑的一种方法是将所有不可扩展的参数包装到其可扩展的子类中(即,定义dict的子类,定义其自己的__hash__函数。)

或者,我正在考虑创建一个dict的子类,它依赖于与hash不同的哈希函数(定义一个递归工作的全局my_hash函数并不难。容器),并使用此子类来存储缓存。但我认为没有一种简单的方法可以做到这一点。

编辑:

我想我会尝试the solution我建议用于python容器的一般散列。有了这个,我应该能够将(*args, **kwargs)的元组包装到自动可以清除的类中,并使用常规的memoization。

2 个答案:

答案 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...