Вопрос проверяет понимание особенностей задания изменяемых объектов в качестве параметров по умолчанию в Python и объясняет, почему это может привести к неожиданным ошибкам.
В Python параметры функции, которым присвоено значение по умолчанию, вычисляются и создаются в момент определения функции (то есть при интерпретации кода, содержащего def), а не при каждом её вызове. Это ключевое отличие от некоторых других языков программирования. Если значением по умолчанию является изменяемый (mutable) объект, такой как список [], словарь {} или множество set(), то этот конкретный объект создаётся один раз и затем используется повторно для всех вызовов функции, где соответствующий аргумент не был указан явно.
Рассмотрим классический пример ошибочной реализации:
def add_item(item, my_list=[]):
my_list.append(item)
return my_list
print(add_item('a')) # Вывод: ['a']
print(add_item('b')) # Вывод: ['a', 'b'] - сюрприз!
Оба вызова использовали один и тот же список my_list, созданный при определении функции. Изменения, сделанные при первом вызове, сохранились и видны при втором. Такое поведение редко является желаемым и часто ведёт к трудноуловимым багам.
Стандартный идиоматический способ обойти эту проблему — использовать в качестве значения по умолчанию неизменяемый объект None, а затем внутри функции проверять это и создавать новый изменяемый объект при необходимости.
def add_item_correct(item, my_list=None):
if my_list is None:
my_list = [] # Создаём новый список при каждом вызове без аргумента
my_list.append(item)
return my_list
print(add_item_correct('a')) # ['a']
print(add_item_correct('b')) # ['b'] - теперь это отдельные списки
Тот же принцип применяется для словарей:
def process_data(data, config=None):
if config is None:
config = {} # Новый словарь для каждого вызова
config.setdefault('verbose', False)
# ... обработка с использованием config
return result
Этот паттерн критически важен при проектировании чистых, предсказуемых функций в Python, особенно в библиотеках и фреймворках. Он предотвращает утечку состояния между, казалось бы, независимыми операциями. Например, функции-обработчики запросов в веб-фреймворках, кэширующие декораторы или рекурсивные функции, накапливающие промежуточные результаты, должны быть аккуратны с параметрами по умолчанию.
Вывод: Всегда используйте None в качестве значения по умолчанию для параметров, которые должны быть изменяемыми объектами. Внутри функции создавайте новый экземпляр списка, словаря или другого контейнера. Это гарантирует изоляцию вызовов и делает поведение функции предсказуемым и безопасным для повторного использования.