Как бороться с циклическими ссылками в Python
Что проверяют
Знаете ли вы, что счётчик ссылок не справляется с циклами, и понимаете ли, как работает второй механизм сборки мусора.
Как отвечать
В CPython основной механизм это подсчёт ссылок: объект удаляется, когда счётчик обнуляется. Цикл (объекты ссылаются друг на друга) счётчик обнулить не может, поэтому существует второй механизм, поколенческий сборщик, который находит недостижимые группы объектов и удаляет их. То есть циклы сами по себе не утечка, они просто освобождаются позже.
Настоящие проблемы появляются в двух случаях: когда цикл содержит объекты с большим весом и сборка откладывается, и когда цикл удерживается извне (глобальные структуры, кеши, замыкания, обработчики событий).
Как бороться:
- слабые ссылки:
weakref.refиweakref.WeakValueDictionaryдля кешей и обратных связей вроде «ребёнок ссылается на родителя». Слабая ссылка не мешает удалению; - разрывать связь явно: методы
closeилиdispose, обнуляющие ссылки, и менеджеры контекста, чтобы это происходило автоматически; - не хранить лишнее: обработчики и подписки снимать, кеши ограничивать (
functools.lru_cacheсmaxsize); - диагностика:
gc.collect()возвращает число собранных объектов,gc.garbageпоказывает то, что собрать не удалось,objgraphиtracemallocпомогают найти, кто держит ссылки.
Отдельно стоит упомянуть, что современный __del__ больше не блокирует сборку циклов, как это было до Python 3.4, но полагаться на него всё равно не стоит: момент вызова не определён.
Где спотыкаются
Считают, что цикл это гарантированная утечка памяти.
Ставят __del__ для освобождения ресурсов вместо менеджера контекста.
Ищут утечку в циклах, хотя память держит глобальный список или кеш без ограничения размера.