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

Что будет если в функции дефолтный параметром объявить пустой список?

1.8 Middle🔥 141 комментариев
#FastAPI и Flask#Базы данных (SQL)#Другое

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

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

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

Проблема с пустым списком как дефолтный параметр

Это классическая ловушка в Python, которая поймала почти каждого разработчика хотя бы один раз. Поведение кажется интуитивным, но результат неожиданный.

Что происходит (неправильное использование)

# ❌ НЕПРАВИЛЬНО: пустой список как дефолтный параметр
def add_item(item, items_list=[]):
    items_list.append(item)
    return items_list

result1 = add_item("apple")
print(result1)  # ['apple']

result2 = add_item("banana")
print(result2)  # ['apple', 'banana']  ← ЭТО НЕОЖИДАННО!

result3 = add_item("orange")
print(result3)  # ['apple', 'banana', 'orange']

# Проблема: один и тот же список переиспользуется!

Кажется, что каждый вызов функции должен создать новый пустой список. На самом деле — список создаётся ровно один раз, при определении функции, и переиспользуется для всех вызовов!

Почему это происходит

# Так Python вычисляет дефолтные параметры
def func(items=[]):
    pass

# Эквивалент (что происходит внутри)
default_list = []  # Создаётся один раз при определении функции
def func(items=default_list):
    pass

# Все вызовы используют ОДИН И ТОТ ЖЕ объект
func()  # items указывает на default_list
func()  # items указывает на ТОТ ЖЕ default_list
func()  # items указывает на ТОТ ЖЕ default_list

Дефолтные параметры вычисляются в момент определения функции, не в момент вызова!

Доказательство проблемы

def add_item(item, items_list=[]):
    items_list.append(item)
    return items_list

# items_list.__repr__() всегда один и тот же объект
add_item("a")
add_item("b")

print(add_item.__defaults__)  # ([a, b],) — видим объект с двумя элементами!

# Или проверим id объекта
def check_id(items_list=[]):
    print(id(items_list))

check_id()  # 140123456789
check_id()  # 140123456789 — ОДИН И ТОТ ЖЕ объект!
check_id()  # 140123456789

Это же касается других изменяемых типов

# ❌ Со словарём — та же проблема
def update_config(key, value, config={}):
    config[key] = value
    return config

update_config("user", "john")
update_config("age", 30)
print(update_config("email", "john@test.com"))  # {'user': 'john', 'age': 30, 'email': ...}

# ❌ С множеством
def add_to_set(item, items=set()):
    items.add(item)
    return items

# Та же проблема!

# ✅ С неизменяемыми типами — всё ОК
def func(value=42):
    return value + 1

func()  # 43
func()  # 43 — никаких проблем

# Число 42 — неизменяемый объект, это безопасно

Правильное решение: None + инициализация

# ✅ ПРАВИЛЬНО: использовать None как дефолтный параметр
def add_item(item, items_list=None):
    if items_list is None:
        items_list = []
    items_list.append(item)
    return items_list

result1 = add_item("apple")
print(result1)  # ['apple']

result2 = add_item("banana")
print(result2)  # ['banana'] — новый список!

result3 = add_item("orange", ["existing"])
print(result3)  # ['existing', 'orange']

# Плюсы:
# - Каждый вызов без аргумента создаёт НОВЫЙ список
# - Можно передать существующий список
# - Логика очевидна

Альтернатива: Type hints и Pydantic

# ✅ Модернее: использовать type hints
from typing import Optional, List

def add_item(item: str, items_list: Optional[List[str]] = None) -> List[str]:
    if items_list is None:
        items_list = []
    items_list.append(item)
    return items_list

# ✅ Или в dataclass
from dataclasses import dataclass, field
from typing import List

@dataclass
class ItemContainer:
    items: List[str] = field(default_factory=list)
    
    def add_item(self, item: str):
        self.items.append(item)

container1 = ItemContainer()
container1.add_item("apple")
print(container1.items)  # ['apple']

container2 = ItemContainer()
container2.add_item("banana")
print(container2.items)  # ['banana'] — новый список!

Best practice: Использовать default_factory

from dataclasses import dataclass, field
from typing import List, Dict

@dataclass
class Config:
    # ✅ Правильно: default_factory создаёт новый объект для каждого экземпляра
    tags: List[str] = field(default_factory=list)
    metadata: Dict[str, str] = field(default_factory=dict)
    
    def add_tag(self, tag: str):
        self.tags.append(tag)

config1 = Config()
config1.add_tag("important")

config2 = Config()
config2.add_tag("urgent")

print(config1.tags)  # ['important']
print(config2.tags)  # ['urgent'] — совершенно разные!

Как запомнить этот принцип

Правило: Дефолтные параметры вычисляются один раз, при определении функции, не при каждом вызове.

# Псевдокод того, что происходит в Python

default_param = []  # Вычисляется ЗДЕСЬ

def func(param=default_param):  # И используется ВЕЗДЕ
    return param

# Это эквивалентно:
shared_list = func.__defaults__[0]
shared_list.append("a")
shared_list.append("b")
shared_list.append("c")

func()  # Всегда возвращает этот же список

Примеры из реальной практики

# ❌ Баг: случайно используем глобальное состояние
class RequestHandler:
    def process(self, data, accumulated_errors=[]):
        # accumulated_errors накапливается между вызовами!
        if not data:
            accumulated_errors.append("empty data")
        return accumulated_errors

handler = RequestHandler()
print(handler.process(""))      # ['empty data']
print(handler.process("ok"))    # ['empty data'] ← БАГ!
print(handler.process(""))      # ['empty data', 'empty data'] ← ЕЩЁ БАГ!

# ✅ Исправленная версия
class RequestHandler:
    def process(self, data, accumulated_errors=None):
        if accumulated_errors is None:
            accumulated_errors = []
        if not data:
            accumulated_errors.append("empty data")
        return accumulated_errors

handler = RequestHandler()
print(handler.process(""))      # ['empty data']
print(handler.process("ok"))    # []
print(handler.process(""))      # ['empty data']

Проверка в IDE и Linters

Модёрные инструменты помогают поймать эту ошибку:

# Ruff (Python linter) даёт предупреждение:
# B006: Do not use mutable data structures for argument defaults

# Mypy (type checker) помогает:
# Explicit Optional[List] показывает, что это может быть None

# IDE (PyCharm, VSCode) могут подсвечивать это

Вывод

Золотое правило:

  • Дефолтные параметры в Python — это объекты, созданные один раз
  • Никогда не используй изменяемые объекты (list, dict, set) как дефолтные параметры
  • Вместо этого используй None + инициализацию или default_factory
  • Это одна из самых частых ошибок в Python, поэтому помни об этом!
Что будет если в функции дефолтный параметром объявить пустой список? | PrepBro