Мутабельный аргумент по умолчанию в Python
@python_quizРазберем этот квиз
Кратко: в Python объекты, указанные в сигнатуре как значения по умолчанию (например, L=[]), создаются один раз — при определении функции — и затем переиспользуются при каждом вызове. Это часто приводит к неожиданному поведению, когда значение по умолчанию — изменяемый объект (list, dict и т.д.).
Условие — код из квиза

Что происходит при выполнении этого кода?
Поведение и объяснение
При определении функции объект списка для параметра L создаётся один раз и остаётся тем же самым между вызовами. Поэтому:
- Первый вызов f(1) добавляет 1 в список по умолчанию и возвращает [1].
- Второй вызов f(2) использует тот же список, добавляет 2 и возвращает [1, 2].
Вывод будет:

Это не ошибка и не неожиданность с точки зрения механизма языка — просто нужно помнить про семантику значений по умолчанию.
Демонстрация того, что список один и тот же (идентичность объекта)

Типичный вывод:

Заметьте: id(L) совпадает — это один и тот же объект.
Разбор вариантов (почему другие предположения неверны)
- Идея "выведет [1] затем [2]": предполагает, что новый список создаётся при каждом вызове. Это верно для неизменяемых значений по умолчанию (или при явном создании нового объекта внутри функции), но не для мутируемых значений, указанных в сигнатуре.
- Идея "выведет [1, 2] затем [1, 2]": требует, чтобы список уже содержал оба элемента до первого вывода — такого не происходит при приведённом коде.
- Идея "произойдёт ошибка из-за мутабельного аргумента": Python не запрещает изменяемые объекты как значения по умолчанию; ошибка здесь не возникает.
Как получить «ожидаемое» поведение (новый список при каждом вызове)
Обычно используется паттерн со значением по умолчанию None:

Теперь при каждом вызове без второго аргумента создаётся новый список.
Когда полезно текущее поведение
Иногда намеренно хотят сохранить состояние между вызовами (например, кэширование, накопление значений). В этом случае мутируемый объект по умолчанию может быть использован сознательно. Но лучше в таких ситуациях явно документировать поведение или использовать более явные конструкции (атрибут функции, класс, замыкание):
Пример с аккумулирующей функцией через атрибут:

Резюме и рекомендации
- Помните: выражения в сигнатуре функции (включая списки и словари) вычисляются один раз при определении функции.
- Не используйте изменяемые объекты в качестве значений по умолчанию, если ожидаете, что при каждом вызове будет новый объект.
- Для «нового» объекта при каждом вызове используйте None и создавайте объект внутри функции.
- Если же вам нужно сохранять состояние между вызовами — делайте это сознательно и явно (атрибут функции, класс, замыкание), чтобы код был понятен другим разработчикам.