Почему использование изменяемых объектов в качестве значений по умолчанию для параметров функций в Python опасно
Что проверяют
Знаете ли вы, что значение по умолчанию вычисляется один раз, и умеете ли обойти это правильно.
Как отвечать
Значение по умолчанию вычисляется один раз, в момент определения функции, и хранится в её атрибуте. При каждом вызове подставляется тот же самый объект. Если он изменяемый, изменения накапливаются между вызовами.
def add(item, target=[]):
target.append(item)
return target
add(1) # [1]
add(2) # [1, 2] а не [2]Функция как будто помнит прошлые вызовы, хотя выглядит чистой. Ошибка коварна тем, что проявляется не сразу: при передаче аргумента явно всё работает правильно, и дефект всплывает только на вызове без аргумента.
Правильный способ:
def add(item, target=None):
if target is None:
target = []
target.append(item)
return targetПустое значение неизменяемо, а новый список создаётся при каждом вызове.
Где встречается та же ловушка:
- словари и множества как значения по умолчанию;
- вызовы функций в значении по умолчанию: время создания объекта зафиксируется на моменте импорта модуля, а не на моменте вызова;
- классы данных: изменяемое значение по умолчанию там запрещено явно, и вместо него требуется фабрика значений;
- атрибуты класса: изменяемый атрибут, объявленный на уровне класса, разделяется всеми экземплярами. Это та же самая природа ошибки в другом обличье.
Полезно упомянуть, что редкие библиотеки используют это поведение намеренно, кэшируя результат в значении по умолчанию. Приём существует, но читается плохо и для прикладного кода не рекомендуется.
Где спотыкаются
Считают, что значение по умолчанию создаётся при каждом вызове.
Ставят пустой список по умолчанию и долго ищут накопление данных.
Объявляют изменяемый атрибут на уровне класса.
Вызывают функцию в значении по умолчанию и получают зафиксированный результат.