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

Кратко: при запуске этот код сначала выведет один список с одним элементом, затем тот же список, в который добавится второй элемент. Ниже подробно разберём почему, проанализируем варианты ответов и покажем безопасные альтернативы.
Что делает код пошагово
- При определении функции Python создаёт объект по умолчанию для аргумента x — в данном случае один пустой список. Это создание происходит один раз, в момент определения функции, а не при каждом вызове.
- При первом вызове f():
- Параметр x ссылается на тот самый список (пустой).
- Выполняется x.append(len(x)). len(x) = 0, в список добавляется 0.
- Функция возвращает список [0].
- При втором вызове f():
- Если не передавать аргумент, x снова ссылается на тот же список, который теперь равен [0].
- Выполняется x.append(len(x)). len(x) = 1, добавляется 1.
- Функция возвращает список [0, 1].
В результате вывод на экран будет:

Почему так происходит (ключевые идеи)
- Аргументы по умолчанию вычисляются один раз — при определении функции.
- Если значение по умолчанию изменяемое (list, dict, set и т.п.), то изменения сохраняются между вызовами.
- Мутация сохраняет ссылку на один и тот же объект, в отличие от переназначения переменной внутри функции.
Анализ вариантов ответов
- "Выведет: [0] [0, 1]" — соответствует реальному поведению программы: первый вызов даёт [0], второй — [0, 1].
- "Выведет: [0] [0]" — предполагает, что каждый вызов получает новый пустой список; это неверно, потому что список по умолчанию общий для всех вызовов.
- "Выведет: [0, 1] [0, 1]" — означало бы, что при первом вызове сразу добавляются два элемента (или что первый вызов возвращает уже изменённый список), что не соответствует порядку выполнения.
- "Поднимет
TypeError" — нет оснований дляTypeError: все операции корректны для списка.
Как избежать подводных камней
Обычно для изменяемых аргументов используют значение по умолчанию None и внутри функции создают новый объект при необходимости:

Теперь каждый вызов без аргумента будет работать с новым списком и выдавать:

Альтернативы:
- Использовать неизменяемые типы в аргументах по умолчанию.
- Явно документировать, что аргумент сохраняет своё состояние между вызовами (редко желаемое поведение, но иногда используется намеренно).
Демонстрация изменений (для наглядности)
Исходный вариант (повторное накопление в одном списке):

Защищённый вариант (каждый вызов независим):

Вывод
Поведение функции объясняется тем, что объект по умолчанию создаётся один раз и затем может быть изменён между вызовами. Это один из классических источников неожиданных багов в Python. Правильная практика — избегать изменяемых объектов в качестве значений по умолчанию, использовать None и инициализировать внутри функции.