Поведение мутабельного аргумента по умолчанию в Python
@python_quizРазберем этот квиз
Кратко: функция использует список как аргумент по умолчанию, и этот объект сохраняется между вызовами. Это даёт неожиданные побочные эффекты, если ожидать, что каждый вызов получает «чистый» пустой список.
Исходный фрагмент кода

Что делает код:
- При n > 0 функция добавляет текущее n в список
acc, затем рекурсивно вызывает себя с n-1 и тем же списком. - Поскольку
acc.append(n)возвращает None, выражение используетorдля вызова рекурсии и в итоге возвращает результат рекурсивного вызова. - Список
accпо умолчанию создаётся один раз при определении функции и переиспользуется между вызовами без передачи явного аргумента.
Пошаговый разбор выполнения
- Первый вызов
f(2):
- acc по умолчанию — [].
- acc.append(2) → acc = [2]
- рекурсивный вызов f(1, [2]):
- acc.append(1) → acc = [2, 1]
- рекурсивный вызов f(0, [2, 1]) возвращает acc → [2, 1]
- Возвращается [2, 1] и печатается.
- Второй вызов
f(1):
- Поскольку аргумент по умолчанию тот же объект acc, он уже равен [2, 1].
- acc.append(1) → acc = [2, 1, 1]
- Рекурсия приводит к возврату [2, 1, 1], и это печатается.
Итого, программа выведет:

Анализ вариантов ответа (строки как в квизе)
- "[2, 1] и [2, 1, 1]"
Соответствует описанному поведению — первый вызов создал [2, 1], второй добавил ещё один 1 в тот же список.
- "[2, 1] и [1]"
Так было бы, если бы для каждого вызова создавался новый пустой список по умолчанию (т. е. без сохранения состояния). В текущем коде это не так.
- "[2, 1] и [2, 1]"
Такое произошло бы, если бы второй вызов не мутировал список или если бы второй вызов использовал копию. В текущем коде список мутирует снова.
- "[2, 1] и [1, 1]"
Невозможный результат для данного кода: второй вызов стартует с уже имеющегося [2,1], добавляет 1 и получает [2,1,1], а не [1,1].
Как исправить (без побочных эффектов)
Рекомендованный паттерн — не использовать мутабельный объект как значение по умолчанию. Вместо этого применяют None и создают новый список внутри функции:

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