Слабые ссылки в Python: почему r() возвращает None

Слабые ссылки в Python: почему r() возвращает None

@python_quiz

Разберем этот квиз

Коротко: приведённый код напечатает True. Ниже объясню, почему так происходит, разберу альтернативные варианты поведения и покажу несколько полезных примеров.

Исходный код

python

Пошагово: что происходит в программе

  1. В функции make() создаётся новый объект o = C(). На объект имеется сильная ссылка o (локальная переменная).
  2. Создаётся слабая ссылка weakref.ref(o) и эта слабая ссылка возвращается наружу.
  3. Когда make() завершает выполнение, локальная переменная o удаляется — сильных ссылок на объект больше нет.
  4. Поскольку слабая ссылка не увеличивает счётчик ссылок, объект становится кандидатурой на удаление сборщиком мусора. В CPython, где используется подсчёт ссылок, объект будет уничтожен немедленно (реализация освобождает память сразу). В других реализациях (например, чистом PyPy) удаление может произойти позже, при явном или автоматическом запуске сборщика мусора.
  5. После уничтожения объекта вызов 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 (немедленное удаление):

python

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

python

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

python

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

python

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

python

Практические замечания

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

Вывод

Слабая ссылка сама по себе не удерживает объект живым. Если после выхода из функции не остаётся сильных ссылок на объект, он будет удалён сборщиком мусора, и вызов слабой ссылки вернёт None. В приведённом фрагменте это приводит к выводу True при проверке r() is None.

Report Page