SkillPy

Изменяемый аргумент по умолчанию в Python: в чём ловушка

Время чтения8 мин чтения
Опубликовано4 сентября 2026 г.
Средне
Python
Функции
Ошибки
Начинающим
Изменяемый аргумент по умолчанию в Python: в чём ловушка

Изменяемый аргумент по умолчанию в Python — это классическая ловушка: если задать параметру значение по умолчанию [] (или {}), список не создаётся заново на каждый вызов, а остаётся общим для всех. В итоге данные незаметно накапливаются между вызовами функции. Разберём, почему так происходит и как это правильно исправить.

Коротко

Не ставь изменяемый объект ([], {}, set()) значением по умолчанию. Используй None, а сам список создавай внутри функции:

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

Как выглядит ловушка

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

print(add_item("яблоко"))
print(add_item("банан"))
['яблоко']
['яблоко', 'банан']

Второй вызов добавил «банан» к списку, в котором уже лежало «яблоко» из первого вызова. Хотя мы ожидали, что каждый вызов без items начнёт с пустого списка. Так проявляется общий на всех список по умолчанию.

Почему так происходит

Значение по умолчанию вычисляется один раз — в момент, когда Python читает строку def и создаёт функцию. Список [] создаётся тогда же, единожды, и «прикрепляется» к функции. Все последующие вызовы без аргумента items работают с этим же самым списком, а не с новым. Поэтому изменения накапливаются от вызова к вызову.

Правильное решение — None

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

print(add_item("яблоко"))
print(add_item("банан"))
['яблоко']
['банан']

None — неизменяемое значение, оно безопасно при установке по умолчанию. Проверка if items is None создаёт новый пустой список при каждом вызове, где аргумент не передан. Теперь функция независима: одинаковый вызов всегда даёт одинаковый результат. Подробнее про параметры и значения по умолчанию — в гайде Функции в Python: от def до lambda.

Где та же ловушка сработает снова

Проблема не только со списками. Любой изменяемый объект в значении по умолчанию ведёт себя так же: словарь {}, множество set(). Все они создаются один раз и разделяются между вызовами. Списки и их изменяемость разобраны в гайде Списки в Python: создание, методы и срезы, а деление типов на изменяемые и неизменяемые — в гайде Типы данных в Python: полный гайд для начинающих.

Когда изменяемый дефолт — не ошибка

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

Короткие ответы на вопросы

Почему список сохраняется между вызовами?

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

Как правильно задать список по умолчанию?

Ставь None как значение по умолчанию, а внутри функции проверяй if items is None: items = []. Так на каждый вызов создаётся новый пустой список.

Это баг Python?

Нет, это документированное поведение — прямое следствие того, что значения по умолчанию вычисляются один раз при создании функции. Знать про него нужно, но «чинить» язык не требуется: достаточно использовать None.

Кот-наставник

— Запомни: значение по умолчанию вычисляется один раз, когда Python читает def. Изменяемый объект — [], {} — там становится общим на все вызовы.

— Надёжное правило без исключений: ставь None, а список или словарь создавай внутри функции. Меньше сюрпризов в проде.

Поймай ловушку раньше, чем она поймает тебя

На SkillPy AI-наставник читает твой код и показывает, где изменяемый аргумент по умолчанию тихо накапливает данные между вызовами — до того, как это превратится в баг. Он не сверяет с шаблоном, а понимает решение и объясняет причину.