Почему в качестве значений по умолчанию в Python функции не рекомендуют использовать изменяемые типы?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Изменяемые типы как значения по умолчанию
Это одна из самых частых ошибок в Python. Изменяемые типы (list, dict, set) не рекомендуют использовать как значения по умолчанию, потому что они создаются один раз при определении функции, а не при каждом вызове.
Проблема
Неправильно:
def add_item(item, items=[]):
items.append(item)
return items
result1 = add_item(1) # [1]
result2 = add_item(2) # [1, 2] — ошибка!
result3 = add_item(3) # [1, 2, 3]
print(result1 is result2) # True — один и тот же объект!
По умолчанию список создаётся один раз при определении функции. Все вызовы используют один и тот же объект. Это приводит к неожиданному поведению и трудноуловимым ошибкам.
Почему это происходит?
def create_list(items=[]):
return items
# __defaults__ содержит значения по умолчанию
print(create_list.__defaults__) # ([],)
# Это один объект для всех вызовов
list1 = create_list()
list1.append(1)
list2 = create_list()
print(list2) # [1] — изменили оригинальный список!
Дефолтные значения — это часть функции, которая вычисляется один раз при определении. Изменяемые объекты остаются в памяти и переиспользуются.
Правильное решение
Вариант 1: None + создание объекта внутри
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
result1 = add_item(1) # [1]
result2 = add_item(2) # [2]
result3 = add_item(3) # [3]
print(result1 is result2) # False — разные объекты
Вариант 2: Использовать с дополнительным параметром
def add_item(item, items=None):
items = items or []
items.append(item)
return items
# Но внимание: это создаст новый список при пустом списке!
result = add_item(1, []) # Создаст новый список
Лучше вариант 1 — явная проверка на None.
С dict
Неправильно:
def create_user(name, metadata={}):
metadata["created"] = True
return metadata
user1 = create_user("Alice") # {"created": True}
user2 = create_user("Bob") # {"created": True} — но где Bob?
print(user1 is user2) # True — один объект!
Правильно:
def create_user(name, metadata=None):
if metadata is None:
metadata = {}
metadata["created"] = True
metadata["name"] = name
return metadata
user1 = create_user("Alice") # {"created": True, "name": "Alice"}
user2 = create_user("Bob") # {"created": True, "name": "Bob"}
Immutable типы как дефолт — OK
Это безопасно:
def greet(name, greeting="Hello"): # str — immutable
return f"{greeting}, {name}!"
def count(n=5): # int — immutable
return n * 2
def process(flag=True): # bool — immutable
return flag
Неизменяемые типы (str, int, tuple, bool) создаются один раз, но их нельзя изменить, поэтому это не вызывает проблем.
Особенность с копированием
def add_item(item, items=[]):
# Плохо: модифицируем дефолт
items.append(item)
return items
def add_item_correct(item, items=None):
# Хорошо
if items is None:
items = []
items.append(item)
return items
# Если передаём список, то модифицируем его
my_list = [1, 2]
result = add_item_correct(3, my_list)
print(my_list) # [1, 2, 3] — оригинальный список изменился!
Если нужна мутация
def add_item(item, items=None):
if items is None:
items = []
new_items = items.copy() # Копируем, не мутируем оригинал
new_items.append(item)
return new_items
my_list = [1, 2]
result = add_item(3, my_list)
print(my_list) # [1, 2] — не изменился
print(result) # [1, 2, 3] — новый список
Type hints для ясности
from typing import Optional, List, Dict
def add_item(item: int, items: Optional[List[int]] = None) -> List[int]:
if items is None:
items = []
items.append(item)
return items
def merge_config(new_config: Dict, base_config: Optional[Dict] = None) -> Dict:
if base_config is None:
base_config = {}
result = {**base_config, **new_config}
return result
Вывод
Правило: Всегда используйте None как дефолт для изменяемых типов и создавайте объект внутри функции. Это избежит трудноуловимых ошибок, связанных с переиспользованием одного объекта для всех вызовов функции.