← Назад к вопросам
Что будет если в функции дефолтный параметром объявить пустой список?
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, поэтому помни об этом!