当函数返回 None 时,使内置 lru_cache 跳过缓存

Max*_*Max 7 python lru python-3.x python-decorators

这是一个简化的函数,我试图为其添加一个lru_cachefor -

from functools import lru_cache, wraps

@lru_cache(maxsize=1000)
def validate_token(token):
    if token % 3:
        return None
    return True

for x in range(1000):
    validate_token(x)

print(validate_token.cache_info())
Run Code Online (Sandbox Code Playgroud)

输出 -

CacheInfo(hits=0, misses=1000, maxsize=1000, currsize=1000)
Run Code Online (Sandbox Code Playgroud)

正如我们所看到的,它也会缓存args返回returnedNone。在上面的示例中,我希望cache_size为 334,我们返回非 None 值。就我而言,我的函数有很大的编号。如果之前的值为 ,则ofargs可能会返回不同的值None所以我想避免缓存None

我想避免重新发明轮子并lru_cache从头开始实施。有什么好的方法可以做到这一点吗?

以下是我的一些尝试 -

1.尝试实现自己的缓存(这里不是lru)-

from functools import wraps 

# global cache object
MY_CACHE = {}

def get_func_hash(func):
    # generates unique key for a function. TODO: fix what if function gets redefined?
    return func.__module__ + '|' + func.__name__

def my_lru_cache(func):
    name = get_func_hash(func)
    if not name in MY_CACHE:
        MY_CACHE[name] = {}
    @wraps(func)
    def function_wrapper(*args, **kwargs):
        if tuple(args) in MY_CACHE[name]:
            return MY_CACHE[name][tuple(args)]
        value = func(*args, **kwargs)
        if value is not None:
            MY_CACHE[name][tuple(args)] = value
        return value
    return function_wrapper

@my_lru_cache
def validate_token(token):
    if token % 3:
        return None
    return True

for x in range(1000):
    validate_token(x)

print(get_func_hash(validate_token))
print(len(MY_CACHE[get_func_hash(validate_token)]))
Run Code Online (Sandbox Code Playgroud)

输出 -

__main__|validate_token
334
Run Code Online (Sandbox Code Playgroud)

2.我意识到,当在包装函数中引发lru_cachean 时,不会进行缓存-exception

from functools import wraps, lru_cache

def my_lru_cache(func):
    @wraps(func)
    @lru_cache(maxsize=1000)
    def function_wrapper(*args, **kwargs):
        value = func(*args, **kwargs)
        if value is None:
            # TODO: change this to a custom exception
            raise KeyError
        return value
    return function_wrapper

def handle_exception(func):
    @wraps(func)
    def function_wrapper(*args, **kwargs):
        try:
            value = func(*args, **kwargs)
            return value
        except KeyError:
            return None
    return function_wrapper    

@handle_exception
@my_lru_cache
def validate_token(token):
    if token % 3:
        return None
    return True

for x in range(1000):
    validate_token(x)

print(validate_token.__wrapped__.cache_info())
Run Code Online (Sandbox Code Playgroud)

输出 -

CacheInfo(hits=0, misses=334, maxsize=1000, currsize=334)
Run Code Online (Sandbox Code Playgroud)

上面正确地仅缓存了334值,但需要将函数包装两次并cache_info以奇怪的方式访问func.__wrapped__.cache_info()

当(或特定)值以Pythonic方式None使用内置装饰器返回时,如何更好地实现不缓存的行为?lru_cache

小智 6

使用异常来防止缓存:

from functools import lru_cache

@lru_cache(maxsize=None)
def fname(x):
    print('worked')

    raise Exception('')

    return 1


for _ in range(10):
    try:
        fname(1)
    except Exception as e:
        pass
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,“worked”将被打印 10 次。


aar*_*ron 4

您缺少此处标记的两行:

def handle_exception(func):
    @wraps(func)
    def function_wrapper(*args, **kwargs):
        try:
            value = func(*args, **kwargs)
            return value
        except KeyError:
            return None

    function_wrapper.cache_info = func.cache_info    # Add this
    function_wrapper.cache_clear = func.cache_clear  # Add this
    return function_wrapper
Run Code Online (Sandbox Code Playgroud)

您可以在一个函数中执行这两个包装器:

def my_lru_cache(maxsize=128, typed=False):
    class CustomException(Exception):
        pass

    def decorator(func):
        @lru_cache(maxsize=maxsize, typed=typed)
        def raise_exception_wrapper(*args, **kwargs):
            value = func(*args, **kwargs)
            if value is None:
                raise CustomException
            return value

        @wraps(func)
        def handle_exception_wrapper(*args, **kwargs):
            try:
                return raise_exception_wrapper(*args, **kwargs)
            except CustomException:
                return None

        handle_exception_wrapper.cache_info = raise_exception_wrapper.cache_info
        handle_exception_wrapper.cache_clear = raise_exception_wrapper.cache_clear
        return handle_exception_wrapper

    if callable(maxsize):
        user_function, maxsize = maxsize, 128
        return decorator(user_function)

    return decorator
Run Code Online (Sandbox Code Playgroud)