Канон / лаборатория 2

Счётчик, цикл и то,
что держит память

Три сценария по шагам: где счётчика достаточно, где он бессилен принципиально и как выглядит настоящая утечка.

к главам 09 и 10шаги детерминированыарифметика счётчика точная

Счётчик ссылок описывается одной фразой — «объект живёт, пока на него смотрят» — и из этой фразы выводится и работающий with, и невозможность собрать цикл, и половина утечек в продакшене. Ниже эта фраза раскручена по шагам. Число в кружке — значение ob_refcnt, то самое поле из первой лаборатории.

1Проверить руками

Второй сценарий воспроизводится целиком и показывает то же самое числами. Вставляется как есть:

import gc, sys

class Node:
    def __init__(self, name): self.name, self.next = name, None
    def __repr__(self): return f"Node({self.name!r})"

a, b = Node("A"), Node("B")
a.next, b.next = b, a
print("rc(A):", sys.getrefcount(a) - 1)   # -1: аргумент getrefcount тоже ссылка
# → rc(A): 2

gc.collect()                 # чистим, чтобы счёт был честным
tracked_before = len(gc.get_objects())
del a, b                     # имён нет, объекты недостижимы
print("собрано счётчиком:", len(gc.get_objects()) - tracked_before)
# → собрано счётчиком: 0      ← ни один не умер

print("собрано обходом:", gc.collect())
# → собрано обходом: 2

2Что из этого следует

Арифметика счётчика в сценариях точная: каждое число здесь — то, что вернёт sys.getrefcount минус единица за собственный аргумент. Порядок разрушения в третьем сценарии — тоже настоящий: CPython разбирает цепочку от внешнего объекта к внутреннему, потому что каждое уничтожение снимает следующую ссылку.