Вопрос с собеседования · Python

Почему нельзя использовать изменяемый объект как значение аргумента по умолчанию?

JuniorФункции
Ещё спрашивают так:
  • Что не так с def f(x=[])?
  • Почему список в аргументе по умолчанию накапливает значения?
Коротко

Значение по умолчанию вычисляется один раз — при определении функции, а не при каждом вызове.

  • Изменяемый объект ([], {}) тогда общий для всех вызовов и копит изменения
  • Правильно: def f(items=None) и внутри if items is None: items = []

А ещё Иногда этим пользуются намеренно — как простым кэшем, но это лучше явно оформить

Подробно

Когда Python выполняет строку def, он создаёт объект функции и в этот же момент вычисляет значения по умолчанию. Они сохраняются в атрибуте __defaults__ и переиспользуются при каждом вызове, где аргумент не передан.

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

Стандартное решение — сентинел None: в сигнатуре None, а новый список создаётся внутри функции при каждом вызове. Если None — допустимое значение аргумента, используют собственный сентинел: _missing = object().

Та же логика касается любых выражений по умолчанию: def log(t=time.time()) запомнит время определения функции, а не вызова.

Пример

def add(item, items=[]):
    items.append(item)
    return items

add(1)   # [1]
add(2)   # [1, 2] — тот же список!

def add(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

Что хотят услышать

  • Причина: значение вычисляется один раз при def.
  • Решение через None и создание объекта внутри.
  • Понимание, что проблема только при изменении объекта.

Частые ошибки

  • Объяснять «Python так кэширует списки» без указания момента вычисления.
  • Решение items = items or []: оно подменит и переданный пустой список, что может быть неожиданно для вызывающего.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✓Значение вычисляется один раз при определении функции и общее для всех вызовов
  • ✗Значение по умолчанию вычисляется заново при каждом вызове функции
  • ✗Python запрещает изменяемые значения по умолчанию
  • ✗Проблема возникает только в многопоточном коде

Что спросят дальше

Разбор темы

Связанные вопросы

Партнёр платформы

Разбор собеседования с sotto

После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.

Разбор собеседования