Мутируемые аргументы по умолчанию в Python
@python_quizРазберем этот квиз
Код из квиза — короткий и коварный:

Что выведет эта программа и почему?
Немедленный вывод
Запуск приведённого кода выведет:

Пояснение — почему именно так происходит.
Разбор механизма
- Аргументы по умолчанию в Python вычисляются один раз — в момент определения функции, а не при каждом её вызове.
- Если значение по умолчанию — изменяемый объект (например, список), то этот же объект будет использоваться при всех последующих вызовах функции, где аргумент не передан.
- В нашем примере
xпри первом вызове — это пустой список, в который добавляетсяlen(x), то есть 0 → список становится[0]. - При втором вызове используется тот же самый список (уже
[0]), поэтомуlen(x)равен 1, добавляется 1 → список становится[0, 1].
Можно убедиться в том, что используется один и тот же объект, распечатав его id:

Вы увидите одинаковые идентификаторы для обоих вызовов.
Анализ возможных результатов (вариантов ответа)
- Вариант с выводом
`
[0]
[0, 1]
`
— соответствует описанному поведению, т.к. список по умолчанию общий для всех вызовов.
- Вариант
`
[0]
[1]
`
был бы верен, если бы дефолтный список создавался заново при каждом вызове (но это не так).
- Вариант
`
[0, 1]
[0, 1]
`
предполагал бы, что при первом вызове функция сразу добавляет и 0, и 1, либо что длина считается иначе — тоже не соответствует семантике кода.
- Вариант с исключением не реализуется: операции с списком (append и len) корректны и не вызовут исключения в данном коде.
Как избежать «ловушки», если нужно новое значение по умолчанию при каждом вызове
Частая идиома — использовать None как маркер и создавать новый список внутри функции:

Теперь каждый вызов без аргумента получает новый список.
Когда такое поведение может быть полезным
Иногда именно сохранение состояния между вызовами полезно — например, для кэширования, аккумулирования результатов или реализации генераторов состояния. Главное — документировать намерение, чтобы код не вводил в заблуждение.
Итог — кратко
- Аргументы по умолчанию вычисляются один раз при определении функции.
- Если значение по умолчанию — изменяемый объект, он будет общим для всех вызовов.
- Чтобы получить новый объект при каждом вызове, используйте None (или другой маркер) и создавайте объект внутри функции.