Изменяемый аргумент по умолчанию в 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-наставник читает твой код и показывает, где изменяемый аргумент по умолчанию тихо накапливает данные между вызовами — до того, как это превратится в баг. Он не сверяет с шаблоном, а понимает решение и объясняет причину.
