Python3 将列表传递给 functools.lru_cache 函数

Python3 pass lists to function with functools.lru_cache

我想缓存一个将列表作为参数的函数,但是当我尝试使用 functools.lru_cache 装饰器时,它失败了 TypeError: unhashable type: 'list'


import functools

@functools.lru_cache()
def example_func(lst):
    return sum(lst) + max(lst) + min(lst)


print(example_func([1, 2]))

这失败了,因为列表是不可散列的。这将使 Python 很难知道缓存了哪些值。解决这个问题的一种方法是在将列表传递给缓存函数之前将列表转换为元组:由于元组是不可变和可哈希的,因此可以缓存它们。

TL;DR

使用元组代替列表:

>>> @lru_cache(maxsize=2)
... def my_function(args):
...     pass
...
>>> my_function([1,2,3])
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    my_function([1,2,3])
TypeError: unhashable type: 'list'

>>> # TO FIX: use a tuple 

>>> my_function(tuple([1,2,3]))
>>>

它不应该抛出错误,而是在用户不知道的情况下在装饰器中转换为可散列的形式。你可以通过像这样装饰你的函数来解决这个问题:

#Custom Decorator function
def listToTuple(function):
    def wrapper(*args):
        args = [tuple(x) if type(x) == list else x for x in args]
        result = function(*args)
        result = tuple(result) if type(result) == list else result
        return result
    return wrapper

#your cached function
@listToTuple
@lru_cache(maxsize=cacheMaxSize)
def checkIfAdminAcquired(self, adminId) -> list:
    query = "SELECT id FROM public.admins WHERE id IN ({}) and 
    confirmed_at IS NOT NULL"
    response = self.handleQuery(query, "int", adminId)
    return response

您可能想在 lru_cache 之后使用另一个装饰器来确保函数的输出不是元组,而是一个列表,因为现在它将 return 元组。

有时,正如当前答案所建议的那样,参数可以采用简单的可散列类型或复杂的不可散列类型,而无需直接转换为可散列。在这种情况下,可能仍然需要将缓存用于可哈希类型的(可能更常见的)情况,而不使用缓存或在不可哈希的情况下出错 - 只需调用底层函数。

这会忽略错误并且通常适用于任何可哈希类型:

import functools

def ignore_unhashable(func): 
    uncached = func.__wrapped__
    attributes = functools.WRAPPER_ASSIGNMENTS + ('cache_info', 'cache_clear')
    @functools.wraps(func, assigned=attributes) 
    def wrapper(*args, **kwargs): 
        try: 
            return func(*args, **kwargs) 
        except TypeError as error: 
            if 'unhashable type' in str(error): 
                return uncached(*args, **kwargs) 
            raise 
    wrapper.__uncached__ = uncached
    return wrapper

使用和测试:

@ignore_unhashable
@functools.lru_cache()
def example_func(lst):
    return sum(lst) + max(lst) + min(lst)

example_func([1, 2]) # 6
example_func.cache_info()
# CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)
example_func((1, 2)) # 6
example_func.cache_info()
# CacheInfo(hits=0, misses=1, maxsize=128, currsize=1)
example_func((1, 2)) # 6
example_func.cache_info()
# CacheInfo(hits=1, misses=1, maxsize=128, currsize=1)

我花了一点时间来理解它,但 example_func.__wrapped__ 是 lru_cache 的版本,example_func.__uncached__ 是原始版本。

如果您不需要LRU,并且所有参数都是引用,您可以使用这个简单的实现。

import time


def cacheRef(f):
    cache = {}

    def g(*args):
        # use `id` to get memory address for function argument.
        cache_key = '-'.join(list(map(lambda e: str(id(e)), args)))
        if cache_key in cache:
            return cache[cache_key]
        v = f(*args)
        cache[cache_key] = v
        return v

    return g


@cacheRef
def someHeavyWork(p1):
    time.sleep(3)
    return ''.join(p1)


l1 = ['a', 'b', 'c']
l2 = ['d', 'e', 'f']

t0 = time.time()
print(int(time.time() - t0), someHeavyWork(l1))
print(int(time.time() - t0), someHeavyWork(l1))
print(int(time.time() - t0), someHeavyWork(l1))
print(int(time.time() - t0), someHeavyWork(l2))
print(int(time.time() - t0), someHeavyWork(l2))
print(int(time.time() - t0), someHeavyWork(l2))

'''
output:
0 abc
3 abc
3 abc
3 def
6 def
6 def
'''