Поведение дефолтных изменяемых аргументов в Python

Поведение дефолтных изменяемых аргументов в Python

@python_quiz

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

Короткий код:

python

Что выведет этот код и почему он так себя ведёт — разберём подробно.

Короткий ответ

Код выведет:

text

Дефолтный изменяемый аргумент создаётся один раз при определении функции, и оба вызова используют один и тот же список.

Пошаговый разбор (интуитивно)

  1. При определении функции f создаётся объект списка [] и привязывается как значение по умолчанию для параметра x.
  2. Первый вызов f(): x указывает на этот единственный список, в него добавляется 1 → список становится [1]. f() возвращает [1].
  3. Второй вызов f(): параметр x снова использует тот же список (уже [1]), в него добавляется ещё один 1 → список становится [1, 1]. f() возвращает [1, 1].
  4. Но в результате print([f(), f()]) оба элемента списка ссылаются на один и тот же изменившийся объект — он уже [1, 1], поэтому итог отображается как [[1, 1], [1, 1]].

Чтобы это лучше увидеть, можно вывести id() списка в каждом вызове:

python

Вы увидите одинаковые id для обоих вызовов, что подтверждает использование одного объекта.

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

  • "Выведет [[1], [1]] — каждый вызов использует новый список"

Не соответствует поведению Python для изменяемых дефолтных аргументов; список не создаётся заново при каждом вызове.

  • "Выведет [[1], [1, 1]] — дефолтный список общий и мутируется"

Частично описывает идею мутации дефолтного списка, но результирующее представление значений в print([f(), f()]) не будет таким: так как оба элемента списка ссылаются на один и тот же объект, они оба отобразят текущее содержимое этого объекта после всех мутаций.

  • "Выведет [[1, 1], [1, 1]] — оба элемента ссылаются на один и тот же изменённый список"

Соответствует истинному поведению: дефолтный список общий, оба места в итоговом списке ссылаются на него после двух добавлений.

  • "Выбросит исключение из-за использования изменяемого аргумента по умолчанию"

Неверно — использование изменяемого аргумента по умолчанию само по себе не вызывает исключение.

Правильные практики (как избежать сюрпризов)

Если вам нужно, чтобы при каждом вызове создавался новый список, применяйте идиому с None:

python

Ещё вариант — использовать неизменяемые дефолты (например, кортежы) или функции-фабрики, но наиболее распространённое и понятное решение — проверка на None.

Вывод

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

Report Page