Короткий ответ
Для объектов без циклов CPython освобождает память сразу, когда счётчик ссылок (refcount) падает до нуля; для объектов, образующих цикл ссылок друг на друга, счётчик ссылок никогда не достигнет нуля сам по себе, поэтому отдельный циклический сборщик мусора (generational garbage collector) периодически находит такие группы объектов и освобождает их — а weakref позволяет держать ссылку на объект, не увеличивая его refcount и не создавая цикл, который мешал бы освобождению памяти.
Как это работает подробнее
Каждый объект в CPython хранит счётчик ссылок; когда он достигает нуля, память освобождается немедленно, детерминированно. Если объект A ссылается на B, а B ссылается на A, их взаимные ссылки держат refcount каждого минимум на 1, даже если снаружи на них больше никто не ссылается — обычный refcounting эту группу никогда не освободит. Модуль gc периодически запускает алгоритм поиска таких недостижимых извне циклов и освобождает их; weakref.ref(obj) создаёт ссылку, которая не увеличивает refcount и становится "мёртвой" (возвращает None), если на объект больше нет обычных (strong) ссылок.
Пример кода
import weakref
class Node:
def __init__(self, name):
self.name = name
self.parent = None
class ParentNode:
def __init__(self, name):
self.name = name
self.children = []
def add_child(self, child):
self.children.append(child)
child.parent = weakref.ref(self) # слабая ссылка — без цикла refcount
p = ParentNode("root")
c = Node("child")
p.add_child(c)
print(c.parent().name) # 'root' — вызываем weakref как функцию, чтобы получить объект
del p
print(c.parent()) # None — родитель уже собран, слабая ссылка это отражаетЧто использовать на практике
- Практика. weakref часто применяют в кэшах (чтобы закешированный объект не продлевал жизнь другому объекту искусственно), в наблюдателях/подписках (listener не должен мешать уборке объекта-источника события) и в структурах с обратными ссылками "родитель-потомок", где потомок хранит ссылку на родителя. Это снижает риск утечек памяти в долгоживущих процессах backend-сервиса, где циклические структуры создаются часто, но зависимость от цикличного GC нежелательна (например, если объекты держат нетривиальные внешние ресурсы, для которых важно освобождение сразу, а не "когда-нибудь" через цикл gc).
Подводные камни
- Риски. Полагают, что в Python утечки памяти в принципе невозможны благодаря автоматическому GC — циклы с объектами, у которых определён кастомный
__del__, в некоторых версиях/ситуациях усложняют и замедляют сборку, а если в сборке участвуют внешние ресурсы (файлы, соединения), их высвобождение может откладываться до срабатывания циклического сборщика. Также путаютweakrefс обычным удалением ссылки — слабая ссылка не продлевает жизнь объекту, но и не удаляет его сама.