Почему опасны изменяемые значения по умолчанию
Вопрос
Объясните, что не так с def f(items=[]) и как это исправить. Бывает ли такое поведение полезным?
Попробуйте сами
Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.
Показать ответ и разбор
Коротко
Что должно прозвучать в ответе
Значение по умолчанию вычисляется один раз при выполнении def
Хранится в `f.__defaults__`, общий объект для всех вызовов
Изменения копятся между вызовами
Исправление: `None` и создание внутри функции
Та же проблема с `datetime.now()` и любым вычисляемым значением
Иногда используют сознательно как простой кеш, но это неочевидно
Что могут спросить следом
- Что будет, если значение по умолчанию кортеж?
- Как линтер ловит эту ошибку?
Разбор
Образцовый ответ: «Выражения значений по умолчанию вычисляются один раз, в момент выполнения def, обычно при импорте модуля. Результат хранится в атрибуте функции __defaults__. Если это изменяемый объект, например список, то все вызовы без этого аргумента работают с одним и тем же списком, и он копит данные между вызовами. Это классический баг, который всплывает в продакшене как утечка данных между запросами. Исправление стандартное: items=None, а внутри if items is None: items = []. Та же история с datetime.now() или uuid4() по умолчанию: значение зафиксируется при импорте. С неизменяемыми значениями, кортежем или строкой, проблемы нет, их нельзя испортить. Иногда трюк используют сознательно как кеш между вызовами, но для этого понятнее functools.cache. Линтеры ruff и pylint ловят изменяемые значения по умолчанию отдельным правилом».
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.
Предыдущий вопрос
Замыкания в Python
Следующий вопрос
UnboundLocalError при изменении глобальной переменной
Термины по теме
Ещё по теме «Функции и области видимости»
- Что такое *args и **kwargs
- Перекрытие встроенных имён
- Как передаются аргументы в функцию Python
- Где хранятся переменные замыкания
- Вызов с *args и **kwargs: разбор сигнатуры
- Композиция функций
- Мемоизация через замыкание
- Повтор вызова с ограничением попыток