Короткий ответ Для объектов без циклов CPython освобождает память сразу, когда счётчик ссылок (refcount) падает до нуля; для объектов, образующих цикл ссылок друг на друга, счётчик ссылок никогда не достигнет нуля сам по себе, поэтому отдельный циклический сборщик мусора (generational garbage collector) периодически находит такие группы объектов и освобождает их — а weakref позволяет держать ссылку на объект, не увеличивая его refcount и не создавая цикл, который мешал бы освобождению памяти. Как это работает подробнее Каждый объект в CPython хранит счётчик ссылок; когда он достигает нуля, память освобождается немедленно, детерминированно. Если объект A ссылается на B, а B ссылается на A, их взаимные ссылки держат refcount каждого минимум на 1, даже если снаружи на них больше никто не ссылается — обычный refcounting эту группу никогда не освободит. Модуль gc периодически запускает алгоритм поиска таких недостижимых извне циклов и освобождает их; weakref.ref(obj) создаёт ссылку, которая не увеличивает refcount