Вопрос проверяет понимание особенностей работы с изменяемыми (mutable) объектами в качестве значений по умолчанию для аргументов функций в Python.
В Python аргументы по умолчанию для функций вычисляются и создаются только один раз — в момент определения функции (обычно при загрузке модуля), а не при каждом её вызове. Это поведение является оптимизацией, но оно приводит к тонкому багу, когда в качестве значения по умолчанию используется изменяемый (mutable) объект, такой как список, словарь или множество.
Определение функции с аргументом по умолчанию def func(arg=[]) связывает имя arg с конкретным объектом списка, созданным в момент определения. Этот объект становится атрибутом функции (func.__defaults__). При каждом вызове, если аргумент не передан, используется ссылка на этот один и тот же объект из __defaults__. Любые модификации этого объекта (append, extend) изменяют его состояние, которое сохраняется для будущих вызовов.
def add_item(item, items=[]):
items.append(item)
return items
print(add_item(1)) # Вывод: [1]
print(add_item(2)) # Вывод: [1, 2] — сюрприз!
print(add_item(3)) # Вывод: [1, 2, 3]
Как видно, список items не создаётся заново при каждом вызове. Вместо этого все добавленные элементы накапливаются в одном и том же списке.
Стандартный паттерн — использовать неизменяемое значение по умолчанию (обычно None), а внутри функции создавать новый изменяемый объект, если аргумент не был передан.
def add_item_safe(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item_safe(1)) # [1]
print(add_item_safe(2)) # [2] — теперь каждый вызов получает новый список
Этот подход гарантирует, что каждый вызов функции без аргумента items начинает работу с нового, пустого списка.
Понимание этого поведения критично для написания надёжного, предсказуемого кода, особенно в библиотеках и фреймворках, где функции могут вызываться многократно с разными контекстами. Это классический пример скрытого разделения состояния (shared state), который может приводить к трудноотлавливаемым багам в многопоточных приложениях или долгоживущих процессах (например, в веб-серверах).
Вывод: Всегда используйте None в качестве значения по умолчанию для аргументов, которые должны быть изменяемыми коллекциями, и создавайте новый объект внутри тела функции. Это делает поведение функции идиоматичным и безопасным.