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

Разберём, почему этот фрагмент ведёт себя именно так и как получить ожидаемое поведение.
Что происходит — пошагово
- Значение аргументов по умолчанию вычисляется один раз — при определении функции. В данном случае список
[]создаётся единожды и затем переиспользуется при каждом вызовеf()без явного аргумента. - Вызов
f()делаетa.append(1)— то есть изменяет (мутабельно) тот же объект-список. - В выражении
print(f(), f())Python вычисляет аргументы функцииprintслева направо: сначала выполняется первыйf(), затем второйf(). Но важный момент — оба вызова оперируют одним и тем же списком. - После второго вызова список содержит два элемента. Поскольку оба возвращённых значения ссылаются на один и тот же объект, при печати вы увидите обновлённое состояние этого объекта дважды.
Итоговый вывод в консоль будет:

Демонстрация с id(), чтобы увидеть, что это один и тот же объект

Примерный вывод покажет одинаковые id для r1 и r2, а также итоговое содержимое списка — [1, 1] в обоих местах.
Почему некоторые ожидают другой результат
- Ожидание вида
[1] [1]исходит из предположения, что каждый вызов создаёт новую пустую списковую структуру. Но это неверно для аргументов по умолчанию, когда они мутабельны. - Ожидание вида
[1] [1, 1]встречается чаще: люди думают, что первыйf()вернул список[1]и что это "слепок", который не изменится после второго вызова. На самом деле возвращается ссылка на один и тот же объект, и последующие мутации видны во всех ссылках на этот объект.
Как избежать такой неожиданности
Правильный и идиоматичный способ — использовать значение по умолчанию None и создавать новый список внутри функции:

Альтернативы (в зависимости от задачи):
- Возвращать копию списка, если нужно вернуть «снимок» состояния в момент вызова:

- Явно передавать новый список при каждом вызове.
Вывод
Мутабельные объекты в качестве значений аргументов по умолчанию ведут себя как один общий объект для всех вызовов функции. Это удобно в редких случаях, но чаще становится источником багов. Используйте None + инициализацию внутри функции или возвращайте копии, если хотите избежать побочных эффектов.