Этот вопрос проверяет понимание особенностей работы с изменяемыми типами данных в Python и их влияния на поведение функций, что важно для предотвращения трудноуловимых ошибок.
В Python параметры функции могут иметь значения по умолчанию, которые используются, если вызывающий код не предоставляет соответствующий аргумент. Ключевая особенность заключается в том, что эти значения по умолчанию вычисляются и создаются в момент определения функции (когда интерпретатор встречает def), а не при каждом её вызове. Это поведение является оптимизацией, но для изменяемых (mutable) типов данных, таких как списки, словари или множества, оно приводит к неочевидным последствиям.
Поскольку объект-значение по умолчанию создаётся один раз, он становится атрибутом самой функции. Если этот объект изменяется внутри функции (например, в список добавляется элемент), то это изменение сохраняется и будет видно в следующем вызове этой же функции. Таким образом, разные вызовы функции начинают неявно разделять одно и то же изменяемое состояние, что противоречит интуиции, ожидающей, что каждый вызов начинается с "чистого" значения по умолчанию.
def add_item(item, items_list=[]):
items_list.append(item)
return items_list
print(add_item('apple')) # Вывод: ['apple']
print(add_item('banana')) # Вывод: ['apple', 'banana'] - сюрприз!
Во втором вызове функция вернула список, содержащий элемент из первого вызова, потому что использовался один и тот же объект списка, созданный при определении функции.
Стандартное решение — использовать неизменяемое значение None в качестве значения по умолчанию, а затем внутри функции проверять это и создавать новый изменяемый объект при необходимости.
def add_item_fixed(item, items_list=None):
if items_list is None:
items_list = []
items_list.append(item)
return items_list
print(add_item_fixed('apple')) # ['apple']
print(add_item_fixed('banana')) # ['banana'] - теперь корректно
Этот подход гарантирует, что каждый вызов функции, для которого аргумент не был передан явно, получит свой собственный, новый список.
Это знание критически важно при проектировании любых функций, которые могут кэшировать данные, накапливать промежуточные результаты или принимать опциональные контейнеры для данных. Особенно часто эта проблема встречается в декораторах, конструкторах классов и функциях, обрабатывающих конфигурации.
Вывод: Всегда используйте None в качестве значения по умолчанию для параметров, которые должны быть изменяемыми объектами, чтобы избежать нежелательного разделения состояния между вызовами функции и сделать её поведение предсказуемым и изолированным.