Python - 任何人都有一个可以处理不可散列参数的记忆装饰器吗?

Posted

技术标签:

【中文标题】Python - 任何人都有一个可以处理不可散列参数的记忆装饰器吗?【英文标题】:Python - anyone have a memoizing decorator that can handle unhashable arguments? 【发布时间】:2011-06-07 20:25:02 【问题描述】:

我一直在使用以下 memoizing 装饰器(来自伟大的书 Python Algorithms: Mastering Basic Algorithms in the Python Language ... 喜欢它,顺便说一句)。

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 是我想记住的函数,以使此设置更具可扩展性。在没有记忆的情况下实现,它会多次计算依赖级别(例如,在上面的示例中,我认为 'a' 计算了 8 次)。

谢谢,

迈克

【问题讨论】:

好吧,只需将它们作为(args, result) 的元组存储在一个列表中,然后对其进行迭代。不过,正如你所说,它不会很快。 @Thomas K:缓存越多越慢,听起来确实适得其反。 @THC4k:这取决于你想要它做什么。如果您要多次遇到相同的几种可能性,并且该功能是一个大计算量,或者一个缓慢的网络请求,那么它可能已经足够好了。在现代计算机上,它可以在成为问题之前变得相当大。 【参考方案1】:

从技术上讲,您可以通过将dict(或listset)转换为元组来解决此问题。例如:

 key = tuple(the_dict.iteritems())
 key = tuple(the_list)
 key = tuple(sorted(the_set))

 cache[key] = func( .. )

但我不会在 memo 中这样做,我宁愿更改您要在备忘录上使用的功能 - 例如,与其接受 dict,不如他们应该只接受 (key, value) 对,而不是采取他们应该采取的列表或集合*args

【讨论】:

您还需要对字典键/值对进行排序。【参考方案2】:

这是 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']

【讨论】:

@MikeRand:我刚刚用你的例子编辑了我的答案,希望它可以帮助 +1:Pickling 是一种有趣的散列可变对象的方法。我想这会胜过为许多可能性迭代一个列表(因为查找时间),但是对于一些可能的情况,更简单的解决方案的开销更少。 似乎有效,非常感谢。只是为了我的理解:cPickle 是否有效地序列化参数以使序列化成为可散列键? @MikeRand:不客气:),是的,cPickle 只是将函数参数腌制到一个可以作为缓存字典中的键放置的字符串中,cPickle 的特点是它非常快. 只在模块顶部使用import 语句,即从__call__ 中删除它不是更有效吗?【参考方案3】:

没有经过大量测试,但似乎可以工作:

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

【讨论】:

返回与我预期不同的答案。在上面的示例上运行时,结果是 0: ['a', 'b'], 1: ['c'], 2: ['e', 'd', 'f']。不知道为什么“d”会从 1 级移动到 2 级。 @MikeRand:我不知道——我认为可能是因为存储的参数是可变的,所以尝试对它们进行深度复制,但没有效果。将对其进行更多研究,并让您知道和/或修改我的答案。【参考方案4】:

由于没有其他人提到它,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 装饰器,它可以让您将函数视为具有延迟评估值的字典。

【讨论】:

【参考方案5】:

我已经搜索并找到了一个很好的python包。

https://pypi.org/project/memoization/

易于使用 可以使用不可散列的参数 其他有用的选项(生存时间)

【讨论】:

【参考方案6】:

我自己试图解决这个问题,遇到了这个问题,因为这个网站在谷歌搜索结果中很突出,但很遗憾没有一个答案令人满意,所以我自己开发了一个方法,我想分享。

我知道这个问题很老,但我的解决方案很新。

那么你如何记忆那些接受可变的、不可散列的对象的函数呢?

简短的回答:你不知道。那我知道你在想这个答案是关于什么的?

好吧,要记住处理不可散列对象的函数并从中获得最大性能,你绝对不能传递对象本身,而是一些指向该对象的可散列指针,以保证指针将指向该对象。

让我解释一下,Python memoization 是基于字典的,dict 是基于hashmaps 的,总之键必须是可哈希的。可变对象是不可散列的,因此为了记忆接受可变参数的函数,您必须使参数不可变。

现在是关键部分,任何使可变对象不可变的尝试都会因为转换开销而降低性能。所以你不能试图让它们不可变。

现在是破解。 Python 将名称重新绑定到对象,Python 创建对象并将字符串映射到该对象,重新分配一个新变量只是为该对象添加一个新名称,并且一旦创建的文字不可变对象将不会再次创建。

In [12]: ('omo', 114) is ('omo', 114)
<>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
<ipython-input-12-5fa552d1d69b>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
  ('omo', 114) is ('omo', 114)
Out[12]: True

In [18]: b'\1' is b'\1'
<>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
<ipython-input-18-b65ac271a853>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
  b'\1' is b'\1'
Out[18]: True

In [19]: b'\x01' is b'\1'
<>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
<ipython-input-19-33adabcdabac>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
  b'\x01' is b'\1'
Out[19]: True

对于每个对象,什么名称可以保证是唯一且不可更改的?他们的id,就是那个对象的内存地址,并发对象会有不同的内存地址,但是生命周期不重叠的对象可能有相同的地址,但是这种可能性非常小。

简而言之,为了记忆处理不可变对象的函数,您不能直接传递对象,而是传递它们的当前 ID。

但是如何从 id 取回对象呢?使用ctypes

例子:

tree = ('ana',
  224): ('nat', 26): ('ate', 108): ('ted', 573): 0, ('ter', 84): 0,
   ('ati', 383): ('tic', 255): 0, ('tin', 28): 0,
   ('ator', 108): 0, ('naced', 31): 0, ('nage', 23): ('ged', 31): 0,
   ('ger', 45): 0, ('nalic', 43): 0,
 ('ani', 87): ('nimal', 39): 0, ('nison', 28): 0,
 ('ave',
  49): ('ven', 20): ('enal', 40): 0,
   ('ene', 152): ('ned', 50): 0, ('ner', 57): 0,
   ('enic', 44): 0, ('ver',
   23): ('era', 70): ('ral', 73): 0, ('ran', 28): 0, ('ere',
    86): ('red', 165): 0, ('rer', 155): 0, ('eri', 69): ('ric', 75): 0,
    ('rin', 24): 0,
 ('cal',
  290): ('ala', 31): ('lace', 67): 0,
   ('lane', 65): 0,
   ('lary', 31): 0,
   ('late', 58): 0, ('ale', 24): ('lene', 90): 0, ('lery', 26): 0, ('ali',
   36): ('lide', 26): 0,
   ('lify', 36): 0,
   ('line', 88): 0,
   ('lise', 417): 0,
   ('lit', 262): ('ite', 209): 0, ('ity', 698): 0,
   ('lize', 259): 0, ('alogy', 23): 0,
 ('can',
  244): ('ana', 24): ('nade', 26): 0,
   ('nage', 59): 0,
   ('nary', 28): 0,
   ('nate', 103): 0, ('anomy', 25): 0,
 ('car',
  427): ('ara', 41): ('rac', 84): ('ace', 33): 0, ('acy', 60): 0,
   ('rade', 47): 0,
   ('rage', 56): 0,
   ('rate', 76): 0, ('are', 22): ('rely', 26): 0, ('rene', 72): 0, ('ari',
   24): ('rice', 30): 0,
   ('ride', 25): 0,
   ('rify', 24): 0,
   ('rily', 63): 0,
   ('rin', 156): ('ina', 24): 0, ('ine', 168): 0,
   ('rise', 146): 0,
   ('rit', 119): ('ite', 174): 0, ('ity', 118): 0,
   ('rize', 56): 0, ('aro', 25): ('rome', 24): 0, ('rone', 35): 0,
 ('cat',
  226): ('ate', 50): ('tely', 117): 0,
   ('tene', 74): 0,
   ('ter', 153): ('era', 35): 0, ('ery', 72): 0, ('atary', 88): 0,
 ('cer',
  155): ('era', 31): ('rac', 63): ('ace', 33): 0, ('acy', 60): 0,
   ('rage', 39): 0,
   ('rary', 36): 0,
   ('rate', 472): 0, ('erene', 41): 0,
 ('col', 294): ('ole', 21): ('lene', 60): 0, ('lery', 39): 0,
  ('olo', 65): ('logy', 825): 0, ('lose', 23): 0,
  ('olute', 34): 0,
 ('cor',
  422): ('ora', 34): ('rac', 50): ('ace', 33): 0, ('acy', 60): 0,
   ('rage', 23): 0,
   ('rary', 20): 0,
   ('rate', 221): 0, ('oro', 34): ('rone', 29): 0, ('rose', 40): 0,
 ('dec', 350): ('eca', 70): ('case', 22): 0, ('cate', 41): 0,
  ('ecide', 50): 0,
  ('ecul', 28): ('ula', 29): 0, ('ule', 41): 0,
 ('del', 166): ('ela', 20): ('lane', 72): 0, ('late', 111): 0,
  ('eli', 86): ('lide', 23): 0,
   ('line', 126): 0,
   ('lise', 44): 0,
   ('lit', 43): ('ite', 209): 0, ('ity', 698): 0,
  ('elene', 20): 0,
 ('dem',
  187): ('ema', 23): ('mary', 23): 0,
   ('mat', 208): ('ata', 37): 0, ('ate', 111): 0, ('emi',
   44): ('mine', 109): 0, ('mit', 58): ('ite', 64): 0,
    ('ity', 45): 0, ('emo',
   79): ('mony', 133): 0,
   ('more', 62): 0,
   ('mose', 29): 0,
   ('mote', 35): 0, ('emer', 21): ('ere', 24): 0, ('ery', 20): 0,
 ('dep', 181): ('epo', 28): ('pore', 32): 0, ('pose', 50): 0,
  ('epery', 21): 0,
 ('dis', 1259): ('isa', 115): ('sary', 24): 0, ('sate', 78): 0,
  ('isi', 49): ('sine', 79): 0,
   ('sit', 53): ('ite', 61): 0, ('ity', 128): 0,
  ('iso', 44): ('some', 26): 0, ('sory', 37): 0,
  ('isely', 83): 0,
 ('div', 116): ('ive', 52): ('vely', 175): 0, ('very', 107): 0,
  ('ivi', 39): ('vine', 51): 0, ('vity', 55): 0,
 ('epi',
  260): ('pid', 27): ('idal', 34): 0,
   ('ide', 40): ('ded', 20): 0, ('der', 40): 0, ('des', 38): 0,
   ('idic', 26): 0, ('pit', 36): ('ital', 80): 0,
   ('iti', 27): ('tic', 142): 0, ('tis', 108): 0,
   ('itor', 22): 0, ('pica', 39): ('cal', 1198): 0,
   ('can', 25): 0, ('piped', 37): 0,
 ('eve',
  60): ('ven', 26): ('enal', 40): 0,
   ('ene', 152): ('ned', 50): 0, ('ner', 57): 0,
   ('enic', 44): 0, ('ver',
   27): ('era', 70): ('ral', 73): 0, ('ran', 28): 0, ('ere',
    86): ('red', 165): 0, ('rer', 155): 0, ('eri', 69): ('ric', 75): 0,
    ('rin', 24): 0,
 ('gen', 150): ('ene', 56): ('nery', 118): 0, ('nese', 644): 0,
  ('eni', 25): ('nine', 59): 0,
   ('nit', 112): ('ite', 200): 0, ('ity', 93): 0,
   ('nize', 33): 0,
 ('hem',
  139): ('ema', 45): ('mary', 23): 0,
   ('mat', 208): ('ata', 37): 0, ('ate', 111): 0, ('emi',
   56): ('mine', 109): 0, ('mit', 58): ('ite', 64): 0, ('ity', 45): 0,
 ('hom',
  193): ('omo', 114): ('mony', 35): 0,
   ('more', 99): 0,
   ('mote', 49): 0, ('omer', 45): ('ere', 24): 0, ('ery', 20): 0,
 ('lat',
  106): ('ate', 27): ('tely', 117): 0,
   ('tene', 74): 0,
   ('ter', 153): ('era', 35): 0, ('ery', 72): 0, ('ati',
   39): ('tice', 187): 0,
   ('tify', 43): 0,
   ('til', 25): ('ile', 79): 0, ('ily', 37): 0,
   ('tin', 230): ('ina', 22): 0, ('ine', 134): 0,
   ('tise', 129): 0,
   ('tite', 52): 0,
   ('tive', 517): 0,
   ('tize', 59): 0,
 ('mal',
  213): ('ala', 64): ('lace', 67): 0,
   ('lane', 65): 0,
   ('lary', 31): 0,
   ('late', 58): 0, ('ale', 37): ('lene', 90): 0, ('lery', 26): 0,
 ('mar',
  286): ('ara', 21): ('rac', 84): ('ace', 33): 0, ('acy', 60): 0,
   ('rade', 47): 0,
   ('rage', 56): 0,
   ('rate', 76): 0, ('ari', 30): ('rice', 30): 0,
   ('ride', 25): 0,
   ('rify', 24): 0,
   ('rily', 63): 0,
   ('rin', 156): ('ina', 24): 0, ('ine', 168): 0,
   ('rise', 146): 0,
   ('rit', 119): ('ite', 174): 0, ('ity', 118): 0,
   ('rize', 56): 0,
 ('mel', 159): ('ela', 55): ('lane', 72): 0, ('late', 111): 0,
  ('eli', 26): ('lide', 23): 0,
   ('line', 126): 0,
   ('lise', 44): 0,
   ('lit', 43): ('ite', 209): 0, ('ity', 698): 0,
 ('met', 291): ('eta', 154): ('tary', 46): 0, ('tate', 45): 0,
  ('ete', 37): ('tene', 65): 0,
   ('ter', 212): ('era', 35): 0, ('ery', 72): 0,
 ('min', 141): ('ine', 22): ('nery', 80): 0, ('nese', 536): 0,
  ('ini', 49): ('nify', 40): 0,
   ('nine', 66): 0,
   ('nit', 112): ('ite', 200): 0, ('ity', 93): 0,
   ('nize', 41): 0,
 ('mis', 512): ('isa', 49): ('sary', 24): 0, ('sate', 78): 0,
  ('isely', 28): 0,
 ('mon',
  393): ('ona', 49): ('nade', 27): 0,
   ('nage', 28): 0,
   ('nary', 118): 0,
   ('nate', 156): 0, ('one', 30): ('nery', 30): 0, ('nese', 54): 0, ('oni',
   23): ('nide', 27): 0,
   ('nify', 35): 0,
   ('nine', 62): 0,
   ('nit', 108): ('ite', 200): 0, ('ity', 93): 0,
   ('nize', 113): 0, ('ono', 220): ('nomy', 160): 0, ('nose', 33): 0,
 ('mor',
  187): ('ora', 25): ('rac', 50): ('ace', 33): 0, ('acy', 60): 0,
   ('rage', 23): 0,
   ('rary', 20): 0,
   ('rate', 221): 0, ('ori', 20): ('rice', 57): 0,
   ('ride', 48): 0,
   ('rify', 54): 0,
   ('rily', 36): 0,
   ('rin', 71): ('ina', 24): 0, ('ine', 168): 0,
   ('rise', 91): 0,
   ('rit', 63): ('ite', 174): 0, ('ity', 118): 0,
   ('rize', 68): 0,
 ('non',
  306): ('ona', 29): ('nade', 27): 0,
   ('nage', 28): 0,
   ('nary', 118): 0,
   ('nate', 156): 0, ('one', 26): ('nery', 30): 0, ('nese', 54): 0, ('oni',
   21): ('nide', 27): 0,
   ('nify', 35): 0,
   ('nine', 62): 0,
   ('nit', 108): ('ite', 200): 0, ('ity', 93): 0,
   ('nize', 113): 0,
 ('pal',
  263): ('ala', 43): ('lace', 67): 0,
   ('lane', 65): 0,
   ('lary', 31): 0,
   ('late', 58): 0, ('ale', 62): ('lene', 90): 0, ('lery', 26): 0, ('ali',
   25): ('lide', 26): 0,
   ('lify', 36): 0,
   ('line', 88): 0,
   ('lise', 417): 0,
   ('lit', 262): ('ite', 209): 0, ('ity', 698): 0,
   ('lize', 259): 0,
 ('par',
  570): ('ara', 236): ('rac', 84): ('ace', 33): 0, ('acy', 60): 0,
   ('rade', 47): 0,
   ('rage', 56): 0,
   ('rate', 76): 0, ('are', 39): ('rely', 26): 0, ('rene', 72): 0, ('ari',
   31): ('rice', 30): 0,
   ('ride', 25): 0,
   ('rify', 24): 0,
   ('rily', 63): 0,
   ('rin', 156): ('ina', 24): 0, ('ine', 168): 0,
   ('rise', 146): 0,
   ('rit', 119): ('ite', 174): 0, ('ity', 118): 0,
   ('rize', 56): 0, ('aro', 38): ('rome', 24): 0, ('rone', 35): 0,
 ('ped',
  107): ('edi', 35): ('dine', 32): 0,
   ('dit', 73): ('ite', 50): 0, ('ity', 89): 0, ('edate', 29): 0,
 ('per',
  649): ('eri', 198): ('rice', 121): 0,
   ('ride', 56): 0,
   ('rify', 37): 0,
   ('rily', 26): 0,
   ('rin', 268): ('ina', 24): 0, ('ine', 168): 0,
   ('rise', 158): 0,
   ('rit', 173): ('ite', 174): 0, ('ity', 118): 0,
   ('rize', 51): 0, ('erene', 20): 0,
 ('pol',
  384): ('ola', 22): ('lane', 27): 0,
   ('lary', 48): 0,
   ('late', 165): 0, ('ole', 22): ('lene', 60): 0, ('lery', 39): 0, ('oli',
   35): ('lide', 36): 0,
   ('lify', 24): 0,
   ('line', 72): 0,
   ('lise', 79): 0,
   ('lit', 234): ('ite', 209): 0, ('ity', 698): 0,
   ('lize', 22): 0,
 ('rec', 342): ('eca', 24): ('case', 22): 0, ('cate', 41): 0,
  ('ecide', 28): 0,
  ('ecul', 35): ('ula', 29): 0, ('ule', 41): 0,
 ('red',
  145): ('edi', 23): ('dine', 32): 0,
   ('dit', 73): ('ite', 50): 0, ('ity', 89): 0, ('edery', 29): 0, ('educe',
   22): 0,
 ('rel', 113): ('ela', 28): ('lane', 72): 0, ('late', 111): 0,
  ('eli', 47): ('lide', 23): 0,
   ('line', 126): 0,
   ('lise', 44): 0,
   ('lit', 43): ('ite', 209): 0, ('ity', 698): 0,
 ('rem',
  125): ('emi', 32): ('mine', 109): 0,
   ('mit', 58): ('ite', 64): 0, ('ity', 45): 0, ('emo',
   35): ('mony', 133): 0, ('more', 62): 0, ('mose', 29): 0, ('mote',
    35): 0, ('emer', 26): ('ere', 24): 0, ('ery', 20): 0,
 ('rep', 241): ('epo', 25): ('pore', 32): 0, ('pose', 50): 0,
  ('epate', 32): 0,
  ('epery', 40): 0,
 ('res',
  299): ('esi', 53): ('side', 44): 0,
   ('sine', 22): 0,
   ('sit', 25): ('ite', 61): 0, ('ity', 128): 0, ('esome', 37): 0,
 ('ret', 180): ('eta', 21): ('tary', 46): 0, ('tate', 45): 0,
  ('eti', 47): ('tice', 150): 0,
   ('tin', 71): ('ina', 22): 0, ('ine', 134): 0,
   ('tise', 60): 0,
   ('tite', 38): 0,
   ('tive', 24): 0,
   ('tize', 34): 0,
 ('rev', 156): ('eve', 76): ('vely', 36): 0, ('very', 139): 0,
  ('evity', 44): 0,
 ('sal',
  178): ('ala', 20): ('lace', 67): 0,
   ('lane', 65): 0,
   ('lary', 31): 0,
   ('late', 58): 0, ('ali', 47): ('lide', 26): 0,
   ('lify', 36): 0,
   ('line', 88): 0,
   ('lise', 417): 0,
   ('lit', 262): ('ite', 209): 0, ('ity', 698): 0,
   ('lize', 259): 0,
 ('sol',
  154): ('ola', 21): ('lane', 27): 0,
   ('lary', 48): 0,
   ('late', 165): 0, ('ole', 36): ('lene', 60): 0, ('lery', 39): 0, ('oli',
   46): ('lide', 36): 0,
   ('lify', 24): 0,
   ('line', 72): 0,
   ('lise', 79): 0,
   ('lit', 234): ('ite', 209): 0, ('ity', 698): 0,
   ('lize', 22): 0,
 ('uni', 217): ('nimal', 31): 0,
  ('nine', 62): ('ned', 88): 0, ('ner', 74): 0, ('net', 25): 0,
  ('niver', 25): 0,
 ('abor', 53): ('oral', 49): 0,
  ('ore', 35): ('red', 42): 0, ('rer', 28): 0,
  ('oric', 35): 0,
 ('acanic', 67): 0,
 ('acet', 75): ('etal', 23): 0, ('etic', 25): 0, ('eton', 22): 0,
 ('acid', 42): ('idal', 43): 0,
  ('ide', 43): ('ded', 20): 0, ('der', 40): 0, ('des', 38): 0,
  ('idic', 49): 0,
 ('adular', 37): 0,
 ('amen', 67): ('enal', 29): 0,
  ('ene', 23): ('ned', 50): 0, ('ner', 57): 0,
  ('enic', 40): 0,
 ('amor', 48): ('oral', 77): 0,
  ('ore', 23): ('red', 42): 0, ('rer', 28): 0,
  ('oric', 47): 0,
 ('aneman', 64): 0,
 ('anom', 78): ('oman', 71): 0, ('omer', 101): 0, ('omic', 115): 0,
 ('apos', 147): ('ose', 47): ('sed', 28): 0, ('ser', 26): 0,
  ('osis', 124): 0,
 ('aris', 60): ('ise', 24): ('sed', 42): 0, ('ser', 42): 0,
  ('ison', 43): 0,
 ('bala', 146): ('lace', 67): 0,
  ('lane', 65): 0,
  ('lary', 31): 0,
  ('late', 58): 0,
 ('baro', 236): ('rome', 24): 0, ('rone', 35): 0,
 ('basi', 125): ('sile', 22): 0,
  ('sine', 39): 0,
  ('sit', 24): ('ite', 61): 0, ('ity', 128): 0,
 ('bene', 114): ('nery', 118): 0, ('nese', 644): 0,
 ('bili', 86): ('lify', 23): 0,
  ('line', 101): 0,
  ('lise', 47): 0,
  ('lit', 562): ('ite', 209): 0, ('ity', 698): 0,
  ('lize', 53): 0,
 ('camer', 119): ('ere', 24): 0, ('ery', 20): 0,
 ('comer', 549): ('ere', 24): 0, ('ery', 20): 0,
 ('coni', 1359): ('nide', 27): 0,
  ('nify', 35): 0,
  ('nine', 62): 0,
  ('nit', 108): ('ite', 200): 0, ('ity', 93): 0,
  ('nize', 113): 0,
 ('cove', 42): ('vely', 34): 0, ('very', 624): 0,
 ('cyanic', 28): 0,
 ('deno', 144): ('nomy', 43): 0, ('nose', 26): 0,
 ('desi', 262): ('side', 44): 0,
  ('sine', 22): 0,
  ('sit', 25): ('ite', 61): 0, ('ity', 128): 0,
 ('dete', 122): ('tene', 65): 0,
  ('ter', 212): ('era', 35): 0, ('ery', 72): 0,
 ('devity', 102): 0,
 ('dila', 59): ('lane', 54): 0, ('lary', 27): 0, ('late', 117): 0,
 ('dimi', 53): ('mine', 90): 0,
  ('mit', 62): ('ite', 64): 0, ('ity', 45): 0,
 ('direly', 45): 0,
 ('domi', 56): ('mine', 149): 0,
  ('mit', 37): ('ite', 64): 0, ('ity', 45): 0,
 ('evanic', 54): 0,
 ('fami', 31): ('mide', 42): 0,
  ('mine', 146): 0,
  ('mit', 36): ('ite', 64): 0, ('ity', 45): 0,
 ('fili', 82): ('lify', 23): 0,
  ('line', 101): 0,
  ('lise', 47): 0,
  ('lit', 562): ('ite', 209): 0, ('ity', 698): 0,
  ('lize', 53): 0,
 ('firely', 69): 0,
 ('forely', 469): 0,
 ('habite', 35): 0,
 ('heli', 132): ('lide', 23): 0,
  ('line', 126): 0,
  ('lise', 44): 0,
  ('lit', 43): ('ite', 209): 0, ('ity', 698): 0,
 ('herene', 192): 0,
 ('hete', 97): ('tene', 65): 0,
  ('ter', 212): ('era', 35): 0, ('ery', 72): 0,
 ('holo', 96): ('logy', 825): 0, ('lose', 23): 0,
 ('hone', 48): ('nery', 30): 0, ('nese', 54): 0,
 ('humat', 98): ('ata', 37): 0, ('ate', 111): 0,
 ('hypery', 246): 0,
 ('icon', 26): ('onal', 20): 0, ('onic', 100): 0,
 ('iner', 181): ('era', 134): ('ral', 73): 0, ('ran', 28): 0,
  ('ere', 24): ('red', 165): 0, ('rer', 155): 0,
  ('eri', 34): ('ric', 75): 0, ('rin', 24): 0,
  ('eron', 27): 0,
 ('iron', 30): ('onal', 44): 0,
  ('one', 47): ('ned', 86): 0,
   ('ner', 85): 0,
   ('nes', 26): 0,
   ('net', 20): 0,
  ('onic', 94): 0,
 ('labite', 70): 0,
 ('lacele', 128): 0,
 ('lamer', 117): ('ere', 24): 0, ('ery', 20): 0,
 ('legate', 76): 0,
 ('lepine', 69): 0,
 ('levity', 57): 0,
 ('line', 113): ('nery', 80): 0, ('nese', 536): 0,
 ('lite', 157): ('tely', 34): 0,
  ('tene', 37): 0,
  ('ter', 89): ('era', 35): 0, ('ery', 72): 0,
 ('live', 34): ('vely', 175): 0, ('very', 107): 0,
 ('magite', 115): 0,
 ('mani', 315): ('nify', 32): 0,
  ('nine', 34): 0,
  ('nit', 98): ('ite', 200): 0, ('ity', 93): 0,
  ('nize', 86): 0,
 ('mate', 139): ('tely', 117): 0,
  ('tene', 74): 0,
  ('ter', 153): ('era', 35): 0, ('ery', 72): 0,
 ('medi', 100): ('dine', 32): 0,
  ('dit', 73): ('ite', 50): 0, ('ity', 89): 0,
 ('megate', 61): 0,
 ('memo', 37): ('mony', 133): 0,
  ('more', 62): 0,
  ('mose', 29): 0,
  ('mote', 35): 0,
 ('meri', 125): ('rice', 121): 0,
  ('ride', 56): 0,
  ('rify', 37): 0,
  ('rily', 26): 0,
  ('rin', 268): ('ina', 24): 0, ('ine', 168): 0,
  ('rise', 158): 0,
  ('rit', 173): ('ite', 174): 0, ('ity', 118): 0,
  ('rize', 51): 0,
 ('mesome', 145): 0,
 ('mili', 127): ('lify', 23): 0,
  ('line', 101): 0,
  ('lise', 47): 0,
  ('lit', 562): ('ite', 209): 0, ('ity', 698): 0,
  ('lize', 53): 0,
 ('modery', 70): 0,
 ('moto', 77): ('tom', 169): ('oma', 22): 0, ('ome', 50): 0, ('omy', 99): 0,
  ('tone', 35): 0,
  ('tory', 32): 0,
 ('myri', 56): ('rin', 25): ('ina', 24): 0, ('ine', 168): 0,
  ('rit', 25): ('ite', 174): 0, ('ity', 118): 0,
 ('nati', 66): ('tice', 187): 0,
  ('tify', 43): 0,
  ('til', 25): ('ile', 79): 0, ('ily', 37): 0,
  ('tin', 230): ('ina', 22): 0, ('ine', 134): 0,
  ('tise', 129): 0,
  ('tite', 52): 0,
  ('tive', 517): 0,
  ('tize', 59): 0,
 ('nema', 37): ('mary', 23): 0,
  ('mat', 208): ('ata', 37): 0, ('ate', 111): 0,
 ('odon', 38): ('onal', 31): 0, ('onic', 39): 0,
 ('oper', 49): ('era', 149): ('ral', 73): 0, ('ran', 28): 0,
  ('ere', 69): ('red', 165): 0, ('rer', 155): 0,
  ('eri', 346): ('ric', 75): 0, ('rin', 24): 0,
  ('eron', 59): 0,
 ('opin', 52): ('inal', 38): 0,
  ('ine', 43): ('ned', 88): 0, ('ner', 74): 0, ('net', 25): 0,
  ('inic', 54): 0,
 ('over', 504): ('era', 70): ('ral', 73): 0, ('ran', 28): 0,
  ('ere', 86): ('red', 165): 0, ('rer', 155): 0,
  ('eri', 69): ('ric', 75): 0, ('rin', 24): 0,
 ('pate', 159): ('tely', 117): 0,
  ('tene', 74): 0,
  ('ter', 153): ('era', 35): 0, ('ery', 72): 0,
 ('peni', 247): ('nine', 59): 0,
  ('nit', 112): ('ite', 200): 0, ('ity', 93): 0,
  ('nize', 33): 0,
 ('peta', 124): ('tary', 46): 0, ('tate', 45): 0,
 ('pipery', 36): 0,
 ('pota', 80): ('tary', 32): 0, ('tate', 52): 0,
 ('puri', 117): ('rice', 23): 0,
  ('rify', 27): 0,
  ('rin', 71): ('ina', 24): 0, ('ine', 168): 0,
  ('rise', 89): 0,
  ('rit', 50): ('ite', 174): 0, ('ity', 118): 0,
  ('rize', 22): 0,
 ('radi', 73): ('dine', 58): 0,
  ('dit', 21): ('ite', 50): 0, ('ity', 89): 0,
 ('rati', 71): ('tice', 187): 0,
  ('tify', 43): 0,
  ('til', 25): ('ile', 79): 0, ('ily', 37): 0,
  ('tin', 230): ('ina', 22): 0, ('ine', 134): 0,
  ('tise', 129): 0,
  ('tite', 52): 0,
  ('tive', 517): 0,
  ('tize', 59): 0,
 ('regen', 119): ('ene', 20): 0, ('eny', 35): 0,
 ('romat', 41): ('ata', 37): 0, ('ate', 111): 0,
 ('rosely', 66): 0,
 ('rubi', 60): ('bine', 24): 0, ('bite', 20): 0,
 ('sati', 63): ('tice', 187): 0,
  ('tify', 43): 0,
  ('til', 25): ('ile', 79): 0, ('ily', 37): 0,
  ('tin', 230): ('ina', 22): 0, ('ine', 134): 0,
  ('tise', 129): 0,
  ('tite', 52): 0,
  ('tive', 517): 0,
  ('tize', 59): 0,
 ('secul', 95): ('ula', 29): 0, ('ule', 41): 0,
 ('selene', 69): 0,
 ('semi', 211): ('mine', 109): 0,
  ('mit', 58): ('ite', 64): 0, ('ity', 45): 0,
 ('sepate', 104): 0,
 ('seve', 22): ('vely', 36): 0, ('very', 139): 0,
 ('sidery', 39): 0,
 ('sili', 107): ('lify', 23): 0,
  ('line', 101): 0,
  ('lise', 47): 0,
  ('lit', 562): ('ite', 209): 0, ('ity', 698): 0,
  ('lize', 53): 0,
 ('supery', 351): 0,
 ('telene', 122): 0,
 ('terene', 155): 0,
 ('unaced', 205): 0,
 ('uranic', 42): 0,
 ('vale', 78): ('lene', 90): 0, ('lery', 26): 0,
 ('vapo', 25): ('poda', 36): 0, ('pore', 40): 0, ('pose', 35): 0,
 ('vari', 67): ('rice', 30): 0,
  ('ride', 25): 0,
  ('rify', 24): 0,
  ('rily', 63): 0,
  ('rin', 156): ('ina', 24): 0, ('ine', 168): 0,
  ('rise', 146): 0,
  ('rit', 119): ('ite', 174): 0, ('ity', 118): 0,
  ('rize', 56): 0,
 ('vene', 111): ('nery', 118): 0, ('nese', 644): 0,
 ('visi', 58): ('sine', 79): 0,
  ('sit', 53): ('ite', 61): 0, ('ity', 128): 0,
 ('volute', 92): 0,
 ('wate', 56): ('tely', 117): 0,
  ('tene', 74): 0,
  ('ter', 153): ('era', 35): 0, ('ery', 72): 0,
 ('xylogy', 50): 0
from ctypes import cast, py_object
from functools import lru_cache
                                                   
@lru_cache(maxsize=None)                           
def remap_key(addr):                               
    d = dict()                                     
    obj = cast(addr, py_object).value
    for k, v in obj.items():                       
        if isinstance(v, dict):                    
            v = remap_key(id(v))                   
        d[f'k!r'] = v                            
    return d                                       

def remap_key_(obj):           
    d = dict()                 
    for k, v in obj.items():   
        if isinstance(v, dict):
            v = remap_key_(v)  
        d[f'k!r'] = v        
    return d                   
In [20]: %timeit remap_key_(tree)
767 µs ± 35.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [21]: %timeit remap_key(id(tree))
184 ns ± 2.84 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [22]: remap_key(id(tree)) == remap_key_(tree)
Out[22]: True

显然,如果对象发生变化,那么结果将是错误的,但是所有其他记忆可变对象的技术都会遇到同样的问题。

【讨论】:

以上是关于Python - 任何人都有一个可以处理不可散列参数的记忆装饰器吗?的主要内容,如果未能解决你的问题,请参考以下文章

类型错误:不可散列类型:'sl​​ice' [python] [dictionaries]

Python - 重复数据删除问题:TypeError:不可散列的类型:'numpy.ndarray'

多标签计算类权重 - 不可散列的类型

TypeError:使用一组 UDT 创建 Cassandra Python 驱动程序模型时不可散列的类型 UserType

哈希算法与不可篡改的区块链

列表不可散列,但元组可散列?