Может ли пустой список быть удалён из памяти и когда это происходит в Python?
Что проверяют
Понимаете ли вы, как CPython управляет памятью: подсчёт ссылок плюс сборщик циклов, и чем «объект удалён» отличается от «память вернулась ОС». В data-ml следом часто спрашивают, почему после del df в ноутбуке память процесса не уменьшилась.
Как отвечать
Да, пустой список это обычный объект, и живёт он, пока на него есть ссылки. Решает не пустота, а счётчик.
- подсчёт ссылок: у каждого объекта в CPython есть счётчик.
del x, переприсваивание имени, выход из функции уменьшают его, и когда он доходит до нуля, объект освобождается сразу, без ожидания сборщика; - циклы:
a = []; a.append(a)ссылается сам на себя, счётчик никогда не станет нулём. Такие объекты находит сборщик циклов из модуляgc, он запускается автоматически по порогам аллокаций или вручную черезgc.collect(); - скрытые ссылки:
_в интерактивной консоли, историяOutв Jupyter, замыкания, изменяемые значения по умолчанию, трейсбек последнего исключения, кеши вродеfunctools.lru_cache.
del удаляет имя, а не объект. Счётчик можно посмотреть через sys.getrefcount(x), он покажет на единицу больше из-за ссылки в самом аргументе.
Про возврат памяти. Освобождённый объект списка CPython кладёт в свой пул для повторного использования, мелкие объекты живут в аренах аллокатора pymalloc, и арена отдаётся ОС только целиком пустой. Поэтому RSS процесса после удаления объектов часто не падает, хотя внутри Python память свободна.
Для полноты: в PyPy подсчёта ссылок нет, объект освобождается при сборке мусора, и момент заранее не известен.
Где спотыкаются
Говорят, что del удаляет список из памяти. Если на него ссылается кто-то ещё, например другой список или ячейка ноутбука, объект продолжит жить.
Считают, что всем занимается gc. Основную работу делает подсчёт ссылок, а сборщик нужен только для циклов, и лишние вызовы gc.collect() в цикле обработки данных лишь тормозят.
Ждут, что память вернётся ОС сразу. Крупные буферы NumPy и pandas выделяются системным аллокатором и обычно возвращаются, а мелкие объекты Python остаются в аренах процесса.