Что выведет def f(x, lst=[]): lst.append(x); return lst при двух последовательных вызовах и почему?
Выведет [1], затем [1, 2]. Дефолтный список создаётся один раз при выполнении def и сохраняется в f.__defaults__. Все вызовы без аргумента lst используют тот же объект, потому он накапливает добавления. Безопасный паттерн: lst=None и lst = lst if lst is not None else [].
Точный вывод и почему
def f(x, lst=[]):
lst.append(x)
return lst
print(f(1)) # [1]
print(f(2)) # [1, 2] — тот же список!
print(f(3)) # [1, 2, 3]
print(f.__defaults__) # ([1, 2, 3],) — сидит на функции
Причина: Python вычисляет default values один раз — в момент исполнения def (обычно при импорте модуля). Результаты сохраняются в кортеже func.__defaults__. При каждом вызове без переданного lst функция берёт ссылку на тот самый объект. lst.append мутирует его — изменение видно всем последующим вызовам.
Когда возникает
Ловушка срабатывает для любых mutable default:
def f(x, items=[])def f(x, cfg={})def f(x, seen=set())def f(x, ts=datetime.now())— здесь "ловушка" другая:datetime.now()вычисляется один раз и навсегда.
Безопасный паттерн
def f(x, lst=None):
if lst is None:
lst = []
lst.append(x)
return lst
print(f(1)) # [1]
print(f(2)) # [2]
print(f(3)) # [3]
Sentinel-вариант, когда None — валидное значение:
_MISSING = object()
def f(x, lst=_MISSING):
if lst is _MISSING:
lst = []
lst.append(x)
return lst
В dataclass и pydantic
from dataclasses import dataclass, field
@dataclass
class Cart:
items: list = field(default_factory=list)
# items: list = [] # ValueError при импорте
В pydantic v2 — то же через Field(default_factory=list).
Когда mutable default используют намеренно
Очень редко — как функция-кеш:
def expensive(arg, _cache={}):
if arg not in _cache:
_cache[arg] = compute(arg)
return _cache[arg]
Работает, но functools.lru_cache читается лучше и поддерживает TTL/maxsize.
Подводные камни
- Mutable default между вызовами — баг, который проявляется только при многократном вызове или в long-running процессе.
- В тестах
monkeypatchне помогает — default уже захвачен в__defaults__. datetime.now()/uuid4()/os.environ["X"]в default — фиксируется на импорте, потом «не меняется».- В
@dataclassmutable-default запрещён напрямую (ValueError) — забыватьfield(default_factory=...). - Линтер ruff
B006ловит mutable default — включите в CI.
Common mistakes
- Говорить, что список создаётся при каждом вызове.
- Исправлять через lst=[] внутри сигнатуры другой функции.
- Не связывать поведение с defaults.
What the interviewer is testing
- Предсказывает точный вывод двух вызовов.
- Объясняет момент вычисления default arguments.
- Показывает safe-паттерн через None или sentinel.