Поведение mutable default аргументов в Python — коротко и наглядно
@python_quizРазберем этот квиз
Код из квиза:

На первый взгляд кажется, что два вызова вернут разные списки — один с 1, другой с 2. На самом деле вывод будет такой:

Почему так происходит — разберёмся.
Ключевые идеи
- Параметр со значением по умолчанию вычисляется один раз — при определении функции. В данном случае
lst=[]создаёт один и тот же список, который будет переиспользоваться при каждом вызове без явного второго аргумента. - Списки в Python изменяемы (mutable). Вызов
lst.append(x)меняет содержимое этого единственного списка. - Аргументы в вызове
print(f(1), f(2))вычисляются слева направо: сначала выполняетсяf(1), потомf(2). Но оба вызова работают с одним и тем же списком. Когдаf(1)вернул ссылку на список[1], затемf(2)добавил2в тот же список — и к моменту, когдаprintвыводит значения, оба аргумента ссылаются на список[1, 2].
Пошаговый ход выполнения
- При определении функции создаётся объект списка, привязанный к параметру по умолчанию
lst. - Выполняется
f(1):
- lst.append(1) — общий список становится [1].
- f(1) возвращает ссылку на этот списка (т.е. объект со значением [1]).
- Выполняется
f(2):
- Используется тот же объект lst.
- lst.append(2) — список теперь [1, 2].
- f(2) возвращает ту же ссылку (объект [1, 2]).
printполучает две ссылки на один и тот же объект и выводит его текущее содержимое:[1, 2] [1, 2].
Демонстрация в интерактивном виде

Вывод:

Как избежать неожиданных эффектов
Если вы хотите, чтобы при каждом вызове создавался новый список по умолчанию, используйте None и создавайте список внутри тела функции:

Теперь:

выведет:

Другие варианты безопасного поведения:
- Использовать неизменяемые типы в качестве значений по умолчанию (числа, None, кортежи).
- Если нужно накапливать состояние, быть явным: хранить его в объекте/атрибуте, не в аргументе по умолчанию, чтобы намерение было видно.
Когда это полезно и когда опасно
- Польза: можно использовать мутабельный default как кэш или накопитель состояния внутри функции (намеренно). Но это трюк, который легко приводит к багам и снижает читаемость.
- Опасность: большинство ожидает, что значение по умолчанию — "свежий" объект при каждом вызове. Когда это не так — возникают трудноуловимые ошибки, особенно в коде с тестами и многими вызовами.
Выводы
- Значения по умолчанию в сигнатуре функции создаются единожды.
- Для мутабельных объектов это означает разделяемое состояние между вызовами.
- Чтобы избежать сюрпризов, используйте None (или другие безопасные приёмы) и создавайте новые объекты внутри функции.
Такая простая деталь может привести к тонким багам; понимание семантики дефолтных аргументов — важная часть грамотного программирования на Python.