Используйте functools @lru_cache без указания параметра maxsize

В документации для 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(): ...
... 
>>> 

Я неправильно читаю документацию?


person Tom Hale    schedule 10.11.2017    source источник
comment
Использование @lru_cache без круглых скобок работает, начиная с Python 3.8.   -  person Boris    schedule 06.12.2019


Ответы (3)


Вы должны хотя бы вызвать 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. :

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
person Cédric Julien    schedule 10.11.2017
comment
Спасибо! Где токен user_function определен в вашем коде? - person Tom Hale; 10.11.2017
comment
@TomHale user_fuinction — это имя параметра внутреннего декоратора, это/ваша декорированная функция - person Cédric Julien; 10.11.2017
comment
Ваше здоровье! Я поднял эту проблему на основе вашего ответа. - person Tom Hale; 10.11.2017
comment
@TomHale хорошо, кстати, это не настоящая проблема с кодом, а скорее дыра в документации. - person Cédric Julien; 10.11.2017
comment
Для меня Least Surprise говорит, что большинству декораторов не требуется () после них... Мне очень нравится то, что вы сделали, не требуя их. - person Tom Hale; 10.11.2017
comment
Использование @lru_cache без круглых скобок работает, начиная с Python 3.8. - person Boris; 06.12.2019

Начиная с 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

PS. @lru_cache без аргументов неявно устанавливает max_size в 128. Если вместо этого вы хотите использовать кеш без максимального размера, в Python 3.9 вы можете использовать новый functools.cache декоратор, который действует как lru_cache(max_size=None).

person Boris    schedule 06.12.2019

Подумайте об этом так: lru_cache — это фабрика декораторов. Вы вызываете его (с параметрами или без, но вызываете именно вы), и он дает вам декоратор.

Вызов фабрики декораторов и применение декоратора к одной строке эквивалентно следующему:

with_small_cache = lru_cache(max_size=5)

@with_small_cache
def function():
    ...
person Éric Araujo    schedule 10.11.2017