← Назад к вопросам

Почему в качестве значений по умолчанию в Python функции не рекомендуют использовать изменяемые типы?

1.0 Junior🔥 191 комментариев
#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Изменяемые типы как значения по умолчанию

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

Почему в качестве значений по умолчанию в Python функции не рекомендуют использовать изменяемые типы? | PrepBro