【问题标题】:Python3 pass lists to function with functools.lru_cachePython3 将列表传递给 functools.lru_cache
【发布时间】:2021-11-16 16:29:00
【问题描述】:

我想缓存一个将列表作为参数的函数,但是当我尝试使用 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]))

【问题讨论】:

  • Hashing arrays in Python的可能重复
  • @Alex 只是把它放在这里,因为谷歌搜索这个(“lrucache python 列表”)并没有找到很多。然后我用自定义散列函数制作了一个自定义类。后来我向专业的 Python 开发人员提出了这个问题,他建议使用元组。我确实认为这两个问题是相关的,但不是重复的。

标签: python python-3.x functools


【解决方案1】:

这会失败,因为列表是不可散列的。这将使 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]))
>>>

【讨论】:

【解决方案2】:

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

#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 之后使用另一个装饰器来确保函数的输出不是元组,而是一个列表,因为现在它会返回元组。

【讨论】:

  • args 转换为元组还不够吗?还转换result的目的是什么?
  • 我猜是因为缓存存储为 的组合 所以如果你的结果也不是可散列的,最好让它也可散列...
【解决方案3】:

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

这会忽略错误并通常适用于任何可散列类型:

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__ 是原始版本。

【讨论】:

    【解决方案4】:

    如果你不需要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
    '''
    
    
    

    【讨论】:

    • 这不使用 functools.lru_cache,因此例如设置 maxsize 不起作用。另外,seq 似乎不是 Python 3 中的内置函数,你从哪里获得该函数?
    • @redfast00 感谢您的建议。我的代码只适用于某些情况,我添加了这些情况。
    猜你喜欢
    • 2022-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多