← Назад к вопросам
Почему пустой список не стоит использовать в качестве аргумента функции по умолчанию?
2.0 Middle🔥 161 комментариев
#Python Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Почему пустой список неправильно использовать как аргумент по умолчанию
Это один из самых частых багов в Python, который демонстрирует фундаментальное непонимание того, как работают значения по умолчанию. Проблема касается mutable объектов.
Суть проблемы: Default Argument Evaluation
В Python значения по умолчанию вычисляются ровно один раз — при определении функции, а не при каждом её вызове.
# ❌ НЕПРАВИЛЬНО
def add_item(item, items=[]):
items.append(item)
return items
result1 = add_item(1)
print(result1) # [1]
result2 = add_item(2)
print(result2) # [1, 2] — ОЖИДАЛось [2]!
result3 = add_item(3)
print(result3) # [1, 2, 3] — ОЖИДАЛОСЬ [3]!
# Все вызовы делят ОДИН И ТОТ ЖЕ объект списка!
print(result1 is result2 is result3) # True
Почему это происходит
# Давайте посмотрим как Python это видит
def example_func(item, items=[]):
items.append(item)
return items
# Момент определения функции:
# items=[] — вычисляется один раз
# Этот список сохраняется в example_func.__defaults__
print(f"Функция __defaults__: {example_func.__defaults__}")
# ([], ) — кортеж с одним элементом
default_list = example_func.__defaults__[0]
print(f"ID списка: {id(default_list)}")
# Каждый вызов функции использует этот же объект
for i in range(3):
result = example_func(i)
print(f"ID в результате: {id(result)}")
# Все ID будут одинаковые!
Демонстрация проблемы через объекты
# Можно проверить это с другими mutable типами
# ❌ Со списком
def bad_list(item, items=[]):
items.append(item)
return items
# ❌ Со словарём
def bad_dict(key, value, data={}):
data[key] = value
return data
# ❌ С set
def bad_set(item, items=set()):
items.add(item)
return items
print(bad_list(1)) # [1]
print(bad_list(2)) # [1, 2] — БАГИ!
print(bad_dict(a, 1)) # {a: 1}
print(bad_dict(b, 2)) # {a: 1, b: 2} — БАГ!
print(bad_set(1)) # {1}
print(bad_set(2)) # {1, 2} — БАГ!
Правильное решение
Используй None и инициализируй параметр внутри функции:
# ✅ ПРАВИЛЬНО
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
result1 = add_item(1)
print(result1) # [1]
result2 = add_item(2)
print(result2) # [2] — Теперь правильно!
result3 = add_item(3)
print(result3) # [3]
print(result1 is result2 is result3) # False — разные объекты
Практические примеры
# ❌ Плохо
def register_user(name, tags=[]):
tags.append(name)
return tags
user1 = register_user(Alice)
user2 = register_user(Bob)
print(user1) # [Alice, Bob] — БАГ!
print(user2) # [Alice, Bob]
# ✅ Хорошо
def register_user(name, tags=None):
if tags is None:
tags = []
tags.append(name)
return tags
user1 = register_user(Alice)
user2 = register_user(Bob)
print(user1) # [Alice]
print(user2) # [Bob]
# ❌ Плохо: вложенные функции
def create_cache():
def fetch_user(user_id, cache={}):
if user_id not in cache:
cache[user_id] = fetch_from_db(user_id)
return cache[user_id]
return fetch_user
# Будет внутренний кеш на весь процесс!
# ✅ Хорошо: кеш вне функции
def create_cache():
cache = {}
def fetch_user(user_id):
if user_id not in cache:
cache[user_id] = fetch_from_db(user_id)
return cache[user_id]
return fetch_user, cache # Возвращаем обе
# ❌ Плохо: параметры с побочным эффектом
class User:
def __init__(self, name, permissions=[]):
self.name = name
self.permissions = permissions # Опасно!
user1 = User(Alice)
user1.permissions.append(admin)
user2 = User(Bob)
print(user2.permissions) # [admin] — БАГ!
# ✅ Хорошо: копируем или инициализируем
class User:
def __init__(self, name, permissions=None):
self.name = name
self.permissions = permissions.copy() if permissions else []
Когда это НЕ проблема
# ✅ Immutable объекты безопасны!
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
print(greet("Alice")) # "Hello, Alice!"
print(greet("Bob")) # "Hello, Bob!"
# ✅ Кортежи безопасны
def process(items, defaults=(1, 2, 3)):
return items + defaults
print(process([])) # [1, 2, 3]
print(process([])) # [1, 2, 3]
# ✅ Numbers безопасны
def increment(n=0):
return n + 1
print(increment()) # 1
print(increment()) # 1
Глубокое объяснение через bytecode
import dis
def bad_func(item, items=[]):
items.append(item)
return items
# Посмотрим на bytecode
dis.dis(bad_func)
# При определении функции:
# LOAD_CONST (пустой список [])
# BUILD_TUPLE (с другими defaults)
# MAKE_FUNCTION
# Пустой список создаётся один раз и сохраняется в константу!
def good_func(item, items=None):
if items is None:
items = []
items.append(item)
return items
dis.dis(good_func)
# Здесь [] создаётся КАЖДЫЙ РАЗ в LOAD_CONST и BUILD_LIST
Антипаттерны в реальных проектах
# ❌ FastAPI эндпоинт (если вы не осторожны)
from fastapi import FastAPI
app = FastAPI()
# ❌ НЕПРАВИЛЬНО
@app.post("/add-item")
def add_item(item: str, history=[]):
history.append(item)
return {"history": history} # Будет расти бесконечно!
# ✅ ПРАВИЛЬНО
@app.post("/add-item")
def add_item(item: str):
history = [item]
return {"history": history}
# ❌ Django ORM
def filter_users(status=active, exclude=None):
if exclude is None:
exclude = []
return User.objects.filter(status=status).exclude(id__in=exclude)
# Если использовать exclude=[] как default, он будет разделяться!
Чеклист для code review
# Проверьте эти паттерны:
# ❌ НАЙТИ И ЗАМЕНИТЬ:
from typing import List
def func1(x: List = []): # ❌
pass
def func2(d: dict = {}): # ❌
pass
def func3(s: set = set()): # ❌
pass
# ✅ ПРАВИЛЬНЫЙ ПАТТЕРН:
from typing import List, Optional
def func1(x: Optional[List] = None): # ✅
if x is None:
x = []
pass
def func2(d: Optional[dict] = None): # ✅
if d is None:
d = {}
pass
def func3(s: Optional[set] = None): # ✅
if s is None:
s = set()
pass
Почему это произошло в Python
Это было решением дизайна в CPython для оптимизации:
- Значения по умолчанию вычисляются один раз
- Это экономит время на каждый вызов функции
- Это хорошо для immutable объектов
- Но ловушка для mutable!
Теперь Python警告 об этом в документации, и это один из первых багов, который ловит статический анализатор типов.
Заключение
Золотое правило:
# ❌ НИКОГДА не используй mutable defaults:
# def func(x=[]): # списки
# def func(x={}): # словари
# def func(x=set()): # множества
# ✅ ВСЕГДА используй None:
# def func(x=None):
# if x is None:
# x = []
Этот баг настолько известен, что лучшие практики всегда рекомендуют использовать None для mutable параметров. Это убережёт вас от часов отладки странного поведения!