Слабые ссылки в Python: почему r() возвращает None
@python_quizРазберем этот квиз
Коротко: приведённый код напечатает True. Ниже объясню, почему так происходит, разберу альтернативные варианты поведения и покажу несколько полезных примеров.
Исходный код

Пошагово: что происходит в программе
- В функции
make()создаётся новый объектo = C(). На объект имеется сильная ссылкаo(локальная переменная). - Создаётся слабая ссылка
weakref.ref(o)и эта слабая ссылка возвращается наружу. - Когда
make()завершает выполнение, локальная переменнаяoудаляется — сильных ссылок на объект больше нет. - Поскольку слабая ссылка не увеличивает счётчик ссылок, объект становится кандидатурой на удаление сборщиком мусора. В CPython, где используется подсчёт ссылок, объект будет уничтожен немедленно (реализация освобождает память сразу). В других реализациях (например, чистом PyPy) удаление может произойти позже, при явном или автоматическом запуске сборщика мусора.
- После уничтожения объекта вызов
r()(гдеr— результатweakref.ref) возвращаетNone. Выражениеr() is NoneдаётTrue.
Итого: программа выводит True.
Почему не другие варианты
- Не
False: чтобыr() is NoneвернулоFalse, объект должен всё ещё существовать (должна быть хотя бы одна сильная ссылка). В данном коде сильных ссылок после выхода изmake()нет. - Не
None(в смысле прямой печатиNone):r()возвращаетNone, но мы печатаем результат сравненияr() is None, а это булево значениеTrue/False. Выражениеprint(r() is None)печатаетTrue, а неNone. - Не исключение: создание слабой ссылки возможно, потому что экземпляры
Cподдерживают слабые ссылки. Исключение могло бы возникнуть, если бы объект не поддерживал слабые ссылки (например, у некоторых типов без слота__weakref__), тогдаweakref.ref(o)кинул быTypeError. Но этот классC— обычный класс, проблем нет.
Демонстрации и варианты
1) Поведение в CPython (немедленное удаление):

2) Если сохранить сильную ссылку — объект живёт:

3) Принудительный запуск сборщика мусора (на случай реализации без немедленного освобождения):

4) Что если тип не поддерживает слабые ссылки:

Чтобы разрешить слабые ссылки, нужно добавить слот __weakref__:

Практические замечания
- weakref полезны для кешей, когда нужно отслеживать объекты, но не препятствовать их сбору мусора.
- Всегда проверяйте
if r() is None:перед использованием объекта из слабой ссылки. - Поведение удаления может зависеть от реализации Python: CPython обычно освобождает объекты немедленно из-за подсчёта ссылок; в других реализациях объект может удалиться позже — можно вызвать
gc.collect(), если нужно гарантировать немедленную очистку в тестах. - Некоторые встроенные типы (например,
int,str) и классы с__slots__без__weakref__не поддерживают слабые ссылки.
Вывод
Слабая ссылка сама по себе не удерживает объект живым. Если после выхода из функции не остаётся сильных ссылок на объект, он будет удалён сборщиком мусора, и вызов слабой ссылки вернёт None. В приведённом фрагменте это приводит к выводу True при проверке r() is None.