Поведение mutable аргумента по умолчанию в Python

Поведение mutable аргумента по умолчанию в Python

@python_quiz

Разберем этот квиз

Код квиза:

python

Кратко: при запуске этот код сначала выведет один список с одним элементом, затем тот же список, в который добавится второй элемент. Ниже подробно разберём почему, проанализируем варианты ответов и покажем безопасные альтернативы.

Что делает код пошагово

  1. При определении функции Python создаёт объект по умолчанию для аргумента x — в данном случае один пустой список. Это создание происходит один раз, в момент определения функции, а не при каждом вызове.
  2. При первом вызове f():

- Параметр x ссылается на тот самый список (пустой).
- Выполняется x.append(len(x)). len(x) = 0, в список добавляется 0.
- Функция возвращает список [0].

  1. При втором вызове f():

- Если не передавать аргумент, x снова ссылается на тот же список, который теперь равен [0].
- Выполняется x.append(len(x)). len(x) = 1, добавляется 1.
- Функция возвращает список [0, 1].

В результате вывод на экран будет:

text

Почему так происходит (ключевые идеи)

  • Аргументы по умолчанию вычисляются один раз — при определении функции.
  • Если значение по умолчанию изменяемое (list, dict, set и т.п.), то изменения сохраняются между вызовами.
  • Мутация сохраняет ссылку на один и тот же объект, в отличие от переназначения переменной внутри функции.

Анализ вариантов ответов

  • "Выведет: [0] [0, 1]" — соответствует реальному поведению программы: первый вызов даёт [0], второй — [0, 1].
  • "Выведет: [0] [0]" — предполагает, что каждый вызов получает новый пустой список; это неверно, потому что список по умолчанию общий для всех вызовов.
  • "Выведет: [0, 1] [0, 1]" — означало бы, что при первом вызове сразу добавляются два элемента (или что первый вызов возвращает уже изменённый список), что не соответствует порядку выполнения.
  • "Поднимет TypeError" — нет оснований для TypeError: все операции корректны для списка.

Как избежать подводных камней

Обычно для изменяемых аргументов используют значение по умолчанию None и внутри функции создают новый объект при необходимости:

python

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

text

Альтернативы:

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

Демонстрация изменений (для наглядности)

Исходный вариант (повторное накопление в одном списке):

python

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

python

Вывод

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

Report Page