使用functools的@lru_cache而不指定maxsize参数

时间:2017-11-10 08:14:36

标签: python caching lru functools

documentation for lru_cache给出了函数定义:

@functools.lru_cache(maxsize=128, typed=False)

这告诉我maxsize是可选的。

然而,它不喜欢没有参数被调用:

Python 3.6.3 (default, Oct 24 2017, 14:48:20) 
[GCC 7.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import functools
>>> @functools.lru_cache
... def f(): ...
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.6/functools.py", line 477, in lru_cache
    raise TypeError('Expected maxsize to be an integer or None')
TypeError: Expected maxsize to be an integer or None
 >>> 

使用参数调用很好:

>>> @functools.lru_cache(8)
... def f(): ...
... 
>>> 

我误读了文档吗?

3 个答案:

答案 0 :(得分:8)

你必须至少在没有args的情况下调用lru_cache:

@lru_cache()
def f():
    #content of the function

这样,lru_cache用默认参数初始化。

这是因为python中的装饰器(带有@表示法)是特殊函数,在解析器读取源时会对其进行求值和调用。

当您编写@decorator_name时,您告诉python decorator_name是一个将使用之后定义的函数(或类)调用的函数。 示例:

@my_decorator
def function():
    pass

相当于:

def function():
    pass
decorated_function = my_decorator(function)

lru_cache装饰器有点复杂,因为在包装函数之前,它必须创建缓存(与函数相关),然后用另一个将执行缓存管理的函数包装该函数。 以下是CPython implementation :( / p>)的(简短)代码

def lru_cache(maxsize=128, typed=False):
    # first, there is a test about the type of the parameters
    if maxsize is not None and not isinstance(maxsize, int):
        raise TypeError('Expected maxsize to be an integer or None')
    # then, the decorating function is created, this function will be called each time you'll call the 'cached' function
    def decorating_function(user_function):
        wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)  # in _lru_wrapper is all the magic about the cache management, it is a 2nd layer of decorator
        return update_wrapper(wrapper, user_function)
    return decorating_function

所以,当你只写

@lru_cache
def f():

python名为lru_cache(f),而且最终,它并没有处理这样的事情。

为了使其符合此写入,我们应该添加一个测试来检查第一个参数(maxsize)是否是可调用函数:

def lru_cache(maxsize=128, typed=False):
    # first, there is a test about the type of the parameters
    if callable(maxsize):
        def decorating_function(user_function):
            wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
            return update_wrapper(wrapper, user_function)
        return decorating_function(maxsize) # yes, maxsizeis the function in this case O:)
    if maxsize is not None and not isinstance(maxsize, int):
        raise TypeError('Expected maxsize to be an integer or None')
    # then, the decorating function is created, this function will be called each time you'll call the 'cached' function
    def decorating_function(user_function):
        wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)  # in _lru_wrapper is all the magic about the cache management, it is a 2nd layer of decorator
        return update_wrapper(wrapper, user_function)
    return decorating_function

答案 1 :(得分:2)

在Python 3.8+上,您可以不带括号地使用@lru_cache,因此您的代码段可以正常工作

Python 3.8.0 (default, Oct 28 2019, 16:14:01) 
[GCC 9.2.1 20191008] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import functools
>>> @functools.lru_cache
... def f():
...     return 2
... 
>>> 

在Python 3.7或更低版​​本上,您必须做@lru_cache()(在@lru_cache之后加上括号)。

它是在2019年5月26日提交的b821868e6d909f4805499db519ebc2cdc01cf611中添加的。

答案 2 :(得分:0)

这样想:lru_cache是​​一个装饰工厂。你打电话给它(有或没有参数,但你打电话给它),它给你一个装饰。

调用装饰器工厂并在一行上应用装饰器相当于:

with_small_cache = lru_cache(max_size=5)

@with_small_cache
def function():
    ...