Короткий ответ
Значение-аргумент по умолчанию вычисляется один раз при определении функции, а не при каждом вызове, поэтому изменяемый default (список, словарь) переживает между вызовами и накапливает данные — правильное решение: использовать None как default и создавать новый объект внутри функции.
Как это работает подробнее
Python создаёт объект для значения по умолчанию один раз, в момент выполнения def, и хранит его в __defaults__ функции. Каждый вызов без явного аргумента получает ссылку на этот же самый объект, а не на свежую копию.
Пример кода
example.py
def append_item(item, bucket=[]): # баг: один и тот же список на все вызовы
bucket.append(item)
return bucket
print(append_item(1)) # [1]
print(append_item(2)) # [1, 2] — неожиданно, старое значение осталось
def append_item_fixed(item, bucket=None):
if bucket is None:
bucket = []
bucket.append(item)
return bucket
print(append_item_fixed(1)) # [1]
print(append_item_fixed(2)) # [2] — независимые вызовыЧто использовать на практике
- Практика. Безопасный паттерн —
def f(items=None): items = items if items is not None else []. Это гарантирует новый список на каждый вызов без явного аргумента. Такой же баг возникает с default-словарями и default-множествами, а не только со списками.
Подводные камни
- Риски. Разработчики ожидают, что каждый вызов "начинает с чистого листа", и удивляются, когда данные из предыдущих вызовов появляются в текущем. Баг особенно незаметен в тестах, если тесты выполняются в порядке, который случайно маскирует накопление состояния.