Мутируемые значения по умолчанию в Python

Мутируемые значения по умолчанию в Python

@python_quiz

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

Условие: дана функция, у которой параметр по умолчанию — список. Что выведет следующий код?

python

Короткий ответ: вывод будет

Дальше — подробный разбор, почему так происходит, и как этого избежать, если нужно.

Почему так работает

В Python значения параметров по умолчанию вычисляются один раз — в момент определения функции, а не при каждом вызове. Если это значение является изменяемым объектом (например, список, словарь, множество), и функция изменяет этот объект (например, добавляет элементы), то эти изменения сохраняются между вызовами.

В нашем примере:

  • При определении f создаётся один список L = [].
  • При первом вызове f(1) элемент 1 добавляется в этот список — теперь L == [1].
  • При втором вызове f(2) используется тот же самый список, в который добавляется 2 — в результате L == [1, 2].
  • Таким образом оба вызова возвращают ссылку на один и тот же список: [1, 2] [1, 2].

Демонстрация с выводом

python

Обратите внимание: второй и третий вызовы продолжают накапливать элементы в том же списке.

Как безопасно задать "пустой" параметр по умолчанию

Если ожидается, что значение параметра должно создаваться заново при каждом вызове (например, пустой список), используйте невидимый маркер — обычно None — и создавайте новый объект внутри функции:

python

Это гарантирует, что при каждом вызове будет создан новый список.

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

  • Если вам нужен неизменяемый контейнер по умолчанию — можно использовать кортеж.
  • В некоторых случаях удобно явно копировать входной списка: L = list(L) или L = L.copy() (если требуется избежать побочных эффектов при передаче внешних объектов).

Когда поведение полезно

Иногда хочется именно сохранить состояние между вызовами (например, кэширование, аккумулирование значений). В таких случаях использование изменяемого значения по умолчанию — осознанный приём:

python

Но важно документировать такое поведение, чтобы пользователи функции знали о побочном эффекте.

Частые ошибки и подводные камни

  • Не используйте трюк L = L or [] если вы хотите отличать пустые коллекции от других ложных значений (например, 0, '', False) — это применимо только когда ожидается, что L либо None, либо валидный список.
  • Для многопоточных программ изменения общего объекта требуют синхронизации (блокировки) — иначе возможны гонки.
  • При использовании mutable по умолчанию в библиотечных функциях это может привести к труднонаходимым багам у пользователей.

Вывод

Поведение значений по умолчанию в Python — частый источник сюрпризов. Запомните простое правило: дефолтные изменяемые объекты создаются один раз при определении функции. Если нужен "свежий" объект при каждом вызове, используйте None (или другую явную инициализацию) внутри функции.

Report Page