Python 3通过functools.lru_cache将列表传递给函数

noj0wjuj  于 12个月前  发布在  Python
关注(0)|答案(6)|浏览(139)

我想缓存一个以列表为参数的函数,但是当我尝试使用functools.lru_cache装饰器时,它失败了。

import functools

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

print(example_func([1, 2]))

字符串

jk9hmnmh

jk9hmnmh1#

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

字符串

b4lqfgs4

b4lqfgs42#

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

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

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

字符串
您可能希望在lru_cache之后使用另一个装饰器,以确保函数的输出不是元组,而是列表,因为现在它将返回元组。

b4qexyjb

b4qexyjb3#

有时候,一个参数可以是一个简单的hashable类型,也可以是一个复杂的unhashable类型,而不需要直接转换为hashable,就像现在的答案所建议的那样。在这种情况下,可能仍然需要一个缓存用于hashable类型(可能更常见)的情况,而不需要使用缓存或在unhashable情况下出错-只需调用底层函数。
这会忽略错误,并且通常适用于任何可散列类型:

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

4dc9hkyq

4dc9hkyq4#

您可以将functools.lru_cache扩展为摘要列表、字典等。关键思想是将参数的散列值传递给lru_cache,而不是原始参数。下面是一个示例性的实现,在参数中散列列表和字典。

from functools import lru_cache

def hash_list(l: list) -> int:
    __hash = 0
    for i, e in enumerate(l):
        __hash = hash((__hash, i, hash_item(e)))
    return __hash

def hash_dict(d: dict) -> int:
    __hash = 0
    for k, v in d.items():
        __hash = hash((__hash, k, hash_item(v)))
    return __hash

def hash_item(e) -> int:
    if hasattr(e, '__hash__') and callable(e.__hash__):
        try:
            return hash(e)
        except TypeError:
            pass
    if isinstance(e, (list, set, tuple)):
        return hash_list(list(e))
    elif isinstance(e, (dict)):
        return hash_dict(e)
    else:
        raise TypeError(f'unhashable type: {e.__class__}')

def my_lru_cache(*opts, **kwopts):
    def decorator(func):
        def wrapper(*args, **kwargs):
            __hash = hash_item([id(func)] + list(args) + list(kwargs.items()))

            @lru_cache(*opts, **kwopts)
            def cached_func(args_hash):
                return func(*args, **kwargs)
            
            return cached_func(__hash)
        return wrapper
    return decorator

字符串
使用my_lru_cache与原始lru_cache完全相同。

@my_lru_cache(maxsize=None)
def example_func(lst):
    return sum(lst) + max(lst) + min(lst)

print(example_func([1, 2]))

yvgpqqbh

yvgpqqbh5#

来自@Donatas-Svilpa的答案对我来说很有用,然而,它也需要覆盖关键字参数选项。

def list_to_tuple(function: Callable) -> Any:
    """Custom decorator function, to convert list to a tuple."""

    def wrapper(*args, **kwargs) -> Any:
        args = tuple(tuple(x) if isinstance(x, list) else x for x in args)
        kwargs = {k: tuple(v) if isinstance(v, list) else v for k, v in kwargs.items()}
        result = function(*args, **kwargs)
        result = tuple(result) if isinstance(result, list) else result
        return result

    return wrapper

字符串
我们可以用这种方式

@list_to_tuple
def do_some_work(arg_1: List[Any], arg_2: int) -> List[Any]:
    pass

mqxuamgl

mqxuamgl6#

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

字符串

相关问题