Вчера была опубликована задача про декоратор lru_cache. Несмотря на то, что декораторы
Вчера была опубликована задача про декоратор 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, должны быть хэшируемыми.
Код задачи:
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, должны быть хэшируемыми.
Канал источник:@press_any_button