Имена, ссылки, циклические ссылки и mutable defaults в Python
В Python есть код со списками, ссылками на объекты, циклическими ссылками и mutable default arguments. Как пройтись по нему и объяснить, что останется в памяти и почему?
Ответить самому
Сначала сформулируйте ответ как на собеседовании, затем откройте разбор и оцените себя.
Короткий ответ
В CPython обычные объекты освобождаются reference counting, но циклы не разбираются одним счетчиком ссылок. Для недостижимых циклов нужен cyclic GC. Mutable default argument создается один раз при определении функции, поэтому список переиспользуется между вызовами без явного аргумента.
Полный разбор
В CPython у каждого объекта есть счетчик ссылок. Когда счетчик падает до нуля, объект можно сразу освободить. Это хорошо работает для обычных ацикличных графов объектов.
Проблема возникает, когда объекты ссылаются друг на друга: например, список a содержит ссылку на b, а b содержит ссылку на a. Если внешние переменные перестали указывать на этот цикл, счетчики ссылок внутри цикла все равно не равны нулю, потому что объекты держат друг друга. Reference counting сам по себе такой цикл не освободит.
Для этого в CPython есть cyclic garbage collector. Он периодически ищет группы контейнерных объектов, которые достижимы только друг из друга, но недостижимы из корней программы. Такие циклы можно собрать. При этом освобождение не обязано случиться ровно в момент зануления внешней ссылки.
Mutable default argument — другой классический runtime-подвох. Значение аргумента по умолчанию вычисляется один раз при создании функции, а не при каждом вызове. Поэтому функция вида def f(x=[]): x.append(1); return x будет переиспользовать один и тот же список во всех вызовах без явного x. Правильный паттерн — использовать None как sentinel и создавать новый список внутри функции.
Теория
Вопрос проверяет модель памяти Python: имена ссылаются на объекты, контейнеры хранят ссылки, CPython использует reference counting плюс отдельный cyclic GC, а default arguments являются частью function object.
Типичные ошибки
- Сказать, что любой объект удаляется сразу после присваивания переменной в None.
- Забыть, что циклические ссылки могут держать ненулевые reference counts.
- Думать, что default argument создается заново при каждом вызове функции.
Как отвечать на собеседовании
- Разделяй reference counting и cyclic GC: это разные механизмы.
- Для mutable defaults сразу называй паттерн с None и созданием объекта внутри функции.