Вчера была опубликована задача про декоратор
lru_cache
. Несмотря на то, что декораторы и аргументы функций — популярная тема, задача оказалась непростой. Верно ответили 42% из 19-ти всех проголосовавших.
Код задачи:from functools import lru_cache
@lru_cache
def tricky_function(a, b=[]):
b.append(len(b))
return sum(b) + a
print(tricky_function(1))
print(tricky_function(2))
print(tricky_function(1, [10]))
print(tricky_function(3))
Разбор задачи:Начнём с разбора кода по шагам. Программа использует декоратор
@lru_cache
, который предназначен для кеширования результатов вызова функции. Это полезно, если функция вызывается часто с одними и теми же аргументами.
Декоратор @lru_cacheДекоратор
lru_cache
из модуля
functools
сохраняет результаты вызова функции в специальный кеш. Если функция вызывается повторно с теми же аргументами, то результат берётся из кеша, а не вычисляется заново. Однако ключевое ограничение: аргументы функции должны быть
хэшируемыми (например, числа, строки, кортежи).
Теперь посмотрим, что делает функция
tricky_function
:
1. Принимает два аргумента:
a
и
b
. Аргумент
a
— обычное число, а аргумент
b
по умолчанию — изменяемый список.
2. Добавляет в список
b
новый элемент: текущую длину списка.
3. Возвращает сумму элементов списка
b
плюс значение
a
.
Проблемы в кодеЕсть два нюанса, которые могут привести к неожиданному поведению:
1.
Изменяемый аргумент по умолчанию: Аргумент
b=[]
создаётся один раз при определении функции, а не при каждом вызове. Это значит, что изменения списка
b
сохраняются между вызовами функции.
2.
Нехэшируемый аргумент: Списки в Python — изменяемые и не являются хэшируемыми. Использование их в функции с
@lru_cache
вызовет ошибку, если они будут переданы.
Разберём вызовы функцииПервый вызов:print(tricky_function(1))
Функция вызывается с
a=1
и значением
b
по умолчанию (
[]
). Вот что происходит:
1. Новый элемент (0) добавляется в список
b
, который теперь выглядит так:
[0]
.
2. Вычисляется сумма элементов списка
b
(0) плюс
a
(1).
3. Результат:
1
.
Второй вызов:print(tricky_function(2))
Функция вызывается с
a=2
и всё тем же списком
b
, который уже содержит
[0]
. Теперь:
1. Новый элемент (1) добавляется в список
b
, который становится
[0, 1]
.
2. Вычисляется сумма элементов списка
b
(1) плюс
a
(2).
3. Результат:
3
.
Третий вызов:print(tricky_function(1, [10]))
Функция вызывается с
a=1
и новым списком
b=[10]
. Однако список
b
не является хэшируемым объектом, и при попытке кешировать результат функция вызовет
TypeError
. Программа завершает выполнение с ошибкой на этом этапе.
Четвёртый вызов:print(tricky_function(3))
Этот вызов не выполняется, так как программа уже завершила работу с ошибкой на предыдущем шаге.
Правильный ответ1
3
TypeError: unhashable type: 'list'
Разбор правильного ответа:1. Первый и второй вызовы проходят успешно, так как аргумент
b
по умолчанию используется без проблем.
2. На третьем вызове возникает ошибка, так как списки не могут использоваться в качестве ключей для кеша
lru_cache
.
3. Четвёртый вызов не выполняется из-за завершения программы.
ИтогЭта задача учит важным аспектам работы с аргументами функций и декоратором
lru_cache
:
- Избегайте изменяемых аргументов по умолчанию.
- Помните, что аргументы функции, декорированной
@lru_cache
, должны быть хэшируемыми.