weakref — Слабые ссылки


Модуль weakref позволяет Python программисту создавать объекты слабых ссылок.

Далее термин референт означает объект, на который ссылается слабая ссылка.

Слабой ссылки на объект недостаточно, чтобы сохранить объект живым: когда единственными оставшимися ссылками на референт являются слабые ссылки, сборщик мусора может уничтожить референт и повторно использовать его память для чего-то другого. Однако до тех пор, пока объект не будет фактически уничтожен, слабая ссылка может возвращать объект, даже если на него нет сильных ссылок.

Основное использование слабых ссылок заключается в реализации кешей или отображений, содержащих большие объекты, когда желательно, чтобы большой объект не оставался живым только потому, что он появляется в кеше или отображении.

Например, если у вас есть несколько больших объектов бинарного изображения, вы можете захотеть связать имя с каждым. Если бы вы использовали словарь Python для сопоставления имён с изображениями или изображений с именами, объекты изображения остались бы живыми только потому, что они появились в словарях как значения или ключи. Классы WeakKeyDictionary и WeakValueDictionary, предоставляемые модулем weakref, представляют собой альтернативу, использующую слабые ссылки для создания сопоставлений, которые не поддерживают жизнь объектов только потому, что они появляются в объектах сопоставления. Если, например, объект изображения является значением в WeakValueDictionary, то, когда последние оставшиеся ссылки на данный объект изображения являются слабыми ссылками, содержащимися в слабых сопоставлениях, сборка мусора может восстановить объект, а его соответствующие записи в слабых сопоставлениях просто удаляются.

WeakKeyDictionary и WeakValueDictionary используют слабые ссылки в своей реализации, настраивая функции обратного вызова для слабых ссылок, которые уведомляют слабые словари, когда ключ или значение были очищены сборщиком мусора. WeakSet реализует интерфейс set, но сохраняет слабые ссылки на свои элементы, как это делает WeakKeyDictionary.

finalize предоставляет прямой способ зарегистрировать функцию очистки, которая будет вызываться при сборке мусора. Это проще в использовании, чем настройка функции обратного вызова для необработанной слабой ссылки, поскольку модуль автоматически гарантирует, что финализатор останется активным до тех пор, пока объект не будет собран.

Большинство программ обнаружат, что использование одного из данных слабых типов контейнеров или finalize — это все, что им нужно — обычно нет необходимости напрямую создавать собственные слабые ссылки. Модуль weakref предоставляет машинерию низкого уровня для расширенного использования.

Не на все объекты можно слабо ссылаться; те объекты, которые могут включать экземпляры классов, функции, написанные на Python (но не на C), методы экземпляров, множества, замороженные множества, некоторые файловые объекты, генераторы, объекты типов, сокеты, массивы, очереди, объекты шаблонов регулярных выражений и объекты кода.

Изменено в версии 3.2: Добавлена поддержка thread.lock, threading.Lock и объектов кода.

Некоторые встроенные типы, такие как list и dict, не поддерживают слабые ссылки напрямую, но могут добавить поддержку посредством создания подклассов:

class Dict(dict):
    pass

obj = Dict(red=1, green=2, blue=3)   # на объект слабой ссылки

Детали реализации CPython: Другие встроенные типы, такие как tuple и int, не поддерживают слабые ссылки, даже если они являются подклассами.

Типы расширения можно легко настроить для поддержки слабых ссылок; см. Слабая справочная поддержка.

class weakref.ref(object[, callback])

Возвращает слабую ссылку на object. Исходный объект можно получить, вызвав объект ссылку, если референт ещё жив; если референт больше не существует, вызов объекта ссылки приведёт к возврату None. Если предоставлено callback, а не None, и возвращённый объект weakref все ещё жив, обратный вызов будет вызван, когда объект будет готов к завершению; объект слабой ссылки будет передан в качестве единственного параметра обратного вызова; референт больше не будет доступен.

Допускается создание множества слабых ссылок для одного и того же объекта. Обратные вызовы, зарегистрированные для каждой слабой ссылки, будут вызываться от самого последнего зарегистрированного обратного вызова до самого старого зарегистрированного обратного вызова.

Исключения, вызванные обратным вызовом, будут отмечены в стандартном выводе ошибок, но не могут быть распространены; они обрабатываются точно так же, как и исключения, вызванные методом объекта __del__().

Слабыми ссылками являются хэшируемыми, если object можно хэшировать. Они сохранят свое хеш-значение даже после удаления object. Если hash() вызывается в первый раз только после удаления object, вызов вызовет TypeError.

Слабые ссылки поддерживают тесты на равенство, но не на порядок. Если референты все ещё живы, две ссылки имеют то же отношение равенства, что и их референты (независимо от callback). Если какой-либо референт был удалён, ссылки равны только в том случае, если ссылочные объекты являются одним и тем же объектом.

Это подклассовый тип, а не функция фабрика.

__callback__

Данный атрибут только для чтения возвращает обратный вызов, связанный в данный момент со weakref. Если обратного вызова нет или референт слабой ссылки больше не существует, тогда данный атрибут будет иметь значение None.

Изменено в версии 3.4: Добавлен атрибут __callback__.

weakref.proxy(object[, callback])

Возвращает прокси на object, который использует слабую ссылку. Поддерживает использование прокси в большинстве контекстов вместо того, чтобы требовать явного разыменования, используемого со слабыми ссылочными объектами. Возвращённый объект будет иметь тип ProxyType или CallableProxyType, в зависимости от того, можно ли вызывать object. Прокси-объекты не являются хэшируемыми независимо от референта; это позволяет избежать ряда проблем, связанных с их принципиально изменчивой природой, и предотвратить их использование в качестве ключей словаря. callback совпадает с одноименным параметром функции ref().

Изменено в версии 3.8: Расширена поддержка операторов для прокси-объектов, включая операторы умножения матриц @ и @=.

weakref.getweakrefcount(object)

Возвращает количество слабых ссылок и прокси, которые ссылаются на object.

weakref.getweakrefs(object)

Возвращает список всех слабых ссылок и прокси-объектов, которые ссылаются на object.

class weakref.WeakKeyDictionary([dict])

Класс сопоставления, слабо ссылающийся на ключи. Записи в словаре будут отброшены, когда больше не будет строгой ссылки на ключ. Можно использовать для связывания дополнительных данных с объектом, принадлежащим другим частям приложения, без добавления атрибутов к этим объектам. Это может быть особенно полезно с объектами, которые переопределяют доступ к атрибутам.

Объекты WeakKeyDictionary имеют дополнительный метод, который напрямую предоставляет внутренние ссылки. Не гарантируется, что ссылки будут «живыми» во время их использования, поэтому результат вызова ссылок необходимо проверить перед использованием. Это можно использовать, чтобы избежать создания ссылок, из-за которых сборщик мусора будет хранить ключи дольше, чем это необходимо.

WeakKeyDictionary.keyrefs()

Возвращает итерацию слабых ссылок на ключи.

class weakref.WeakValueDictionary([dict])

Класс сопоставления, слабо ссылающийся на значения. Записи в словаре будут отбрасываться, когда больше не будет строгой ссылки на значение.

Объекты WeakValueDictionary имеют дополнительный метод, который имеет те же проблемы, что и метод keyrefs() объектов WeakKeyDictionary.

WeakValueDictionary.valuerefs()

Возвращает итерацию слабых ссылок на значения.

class weakref.WeakSet([elements])

Устанавливает класс, который сохраняет слабые ссылки на свои элементы. Элемент будет отброшен, когда на него больше не существует строгой ссылки.

class weakref.WeakMethod(method)

Пользовательский подкласс ref, который имитирует слабую ссылку на связанный метод (т. е. метод, определённый в классе и просматриваемый в экземпляре). Поскольку связанный метод эфемерен, стандартная слабая ссылка не может его удержать. WeakMethod имеет специальный код для воссоздания связанного метода до тех пор, пока либо объект, либо исходная функция не умрёт:

>>> class C:
...     def method(self):
...         print("вызывается метод!")
...
>>> c = C()
>>> r = weakref.ref(c.method)
>>> r()
>>> r = weakref.WeakMethod(c.method)
>>> r()
<bound method C.method of <__main__.C object at 0x7fc859830220>>
>>> r()()
вызывается метод!
>>> del c
>>> gc.collect()
0
>>> r()
>>>

Добавлено в версии 3.4.

class weakref.finalize(obj, func, *args, **kwargs)

Возвращает вызываемый объект финализатора, который будет вызываться при сборке мусора obj. В отличие от обычной слабой ссылки, финализатор всегда будет существовать до тех пор, пока не будет собран объект ссылки, что значительно упрощает управление жизненным циклом.

Финализатор считается живым до тех пор, пока он не будет вызван (либо явно, либо при сборке мусора), а после этого он будет мёртв. Вызов активного финализатора возвращает результат вычисления func(*arg, **kwargs), тогда как вызов мёртвого финализатора возвращает None.

Исключения, вызванные обратными вызовами финализатора во время сборки мусора, будут показаны в стандартном выводе ошибок, но не могут быть распространены. Они обрабатываются так же, как исключения, вызванные методом объекта __del__() или обратным вызовом слабой ссылки.

Когда программа завершает работу, вызывается каждый оставшийся работающий финализатор, если его атрибут atexit не имеет значение ложь. Они вызываются в обратном порядке создания.

Финализатор никогда не будет вызывать свой обратный вызов в более поздней части выключение интерпретатора, когда глобальные модули модуля могут быть заменены на None.

__call__()

Если self активен, помечает его как мёртвый и возвращает результат вызова func(*args, **kwargs). Если self мёртв, возвращает None.

detach()

Если self жив, помечает его как мёртвый и возвращает кортеж (obj, func, args, kwargs). Если self мёртв, возвращает None.

peek()

Если self жив, возвращает кортеж (obj, func, args, kwargs). Если self мёртв, возвращает None.

alive

Свойство, содержит значение истина, если финализатор активен, и значение ложь в противном случае.

atexit

Доступное для записи логическое свойство, которое по умолчанию равно истина. Когда программа завершается, она вызывает все оставшиеся живые финализаторы, для которых atexit имеет значение истина. Они вызываются в обратном порядке создания.

Примечание

Важно убедиться, что func, args и kwargs не владеют никакими прямыми или косвенными ссылками на obj, поскольку в противном случае obj никогда не будет удалён сборщиком мусора. В частности, func не должен быть связанным методом obj.

Добавлено в версии 3.4.

weakref.ReferenceType

Объект типа для объектов слабых ссылок.

weakref.ProxyType

Объект типа для прокси объектов, которые нельзя вызывать.

weakref.CallableProxyType

Объект типа для прокси вызываемых объектов.

weakref.ProxyTypes

Последовательность, содержащая все объекты типов для прокси. Может упростить проверку того, является ли объект прокси, не завися от именования обоих типов прокси.

См.также

PEP 205 - Слабые ссылки
Предложение и обоснование этой функции, включая ссылки на ранее реализации и информацию об аналогичных функциях на других языках.

Слабые ссылочные объекты

Объекты слабых ссылок не имеют методов и атрибутов, кроме ref.__callback__. Объект слабой ссылки позволяет получить референт, если он всё ещё существует, путём его вызова:

>>> import weakref
>>> class Object:
...     pass
...
>>> o = Object()
>>> r = weakref.ref(o)
>>> o2 = r()
>>> o is o2
True

Если референт больше не существует, вызов ссылочного объекта возвращает None:

>>> del o, o2
>>> print(r())
None

Проверка того, что объект слабой ссылки все ещё жив, должна выполняться с использованием выражения ref() is not None. Обычно код приложения, которому необходимо использовать ссылочный объект, должен следовать этому шаблону:

# r является объектом слабой ссылки
o = r()
if o is None:
    # референт был собран сборщиком мусора
    print("Объект был освобожден; не могу заморочиться.")
else:
    print("Объект все еще жив!")
    o.do_something_useful()

Использование отдельного теста на «живучесть» создаёт условия гонки в многопоточных приложениях; другой поток может привести к тому, что слабая ссылка станет недействительной до того, как слабая ссылка будет вызвана; Показанная выше идиома безопасна как в многопоточных, так и в однопоточных приложениях.

Специализированные версии объектов ref могут быть созданы путём создания подклассов. Это используется в реализации WeakValueDictionary, чтобы уменьшить нагрузку на память для каждой записи в отображении. Это может быть наиболее полезно для связывания дополнительной информации со ссылкой, но также может быть использовано для дополнительной обработки вызовов для получения ссылки.

В этом примере показано, как можно использовать подкласс ref для хранения дополнительной информации об объекте и воздействия на значение, возвращаемое при доступе к референту:

import weakref

class ExtendedRef(weakref.ref):
    def __init__(self, ob, callback=None, /, **annotations):
        super(ExtendedRef, self).__init__(ob, callback)
        self.__counter = 0
        for k, v in annotations.items():
            setattr(self, k, v)

    def __call__(self):
        """Возвращает пару, содержащую референт и количество вызовов ссылки.
        """
        ob = super(ExtendedRef, self).__call__()
        if ob is not None:
            self.__counter += 1
            ob = (ob, self.__counter)
        return ob

Пример

Данный простой пример показывает, как приложение может использовать ID объектов для извлечения объектов, которые оно видело ранее. ID объектов могут затем использоваться в других структурах данных, не заставляя объекты оставаться в живых, но объекты все равно могут быть получены по ID, если они это делают.

import weakref

_id2obj_dict = weakref.WeakValueDictionary()

def remember(obj):
    oid = id(obj)
    _id2obj_dict[oid] = obj
    return oid

def id2obj(oid):
    return _id2obj_dict[oid]

Объекты финализатора

Основное преимущество использования finalize заключается в том, что он упрощает регистрацию обратного вызова без необходимости сохранения возвращаемого объекта финализатора. Например

>>> import weakref
>>> class Object:
...     pass
...
>>> kenny = Object()
>>> weakref.finalize(kenny, print, "You killed Kenny!")  #doctest:+ELLIPSIS
<finalize object at ...; for 'Object' at ...>
>>> del kenny
You killed Kenny!

Финализатор также может быть вызван напрямую. Однако финализатор вызовет обратный вызов не более одного раза.

>>> def callback(x, y, z):
...     print("CALLBACK")
...     return x + y + z
...
>>> obj = Object()
>>> f = weakref.finalize(obj, callback, 1, 2, z=3)
>>> assert f.alive
>>> assert f() == 6
CALLBACK
>>> assert not f.alive
>>> f()                     # обратный вызов не вызывается, потому что финализатор мертв
>>> del obj                 # обратный вызов не вызывается, потому что финализатор мертв

Вы можете отменить регистрацию финализатора, используя его метод detach(). Это убивает финализатор и возвращает аргументы, переданные конструктору при его создании.

>>> obj = Object()
>>> f = weakref.finalize(obj, callback, 1, 2, z=3)
>>> f.detach()                                           #doctest:+ELLIPSIS
(<...Object object ...>, <function callback ...>, (1, 2), {'z': 3})
>>> newobj, func, args, kwargs = _
>>> assert not f.alive
>>> assert newobj is obj
>>> assert func(*args, **kwargs) == 6
CALLBACK

Если вы не устанавливает для атрибута atexit значение False, финализатор будет вызываться при выходе из программы, если он ещё жив. Например

>>> obj = Object()
>>> weakref.finalize(obj, print, "obj dead or exiting")
<finalize object at ...; for 'Object' at ...>
>>> exit()
obj dead or exiting

Сравнение финализаторов с методами __del__()

Предположим, мы хотим создать класс, экземпляры которого представляют временные каталоги. Каталоги должны быть удалены вместе с их содержимым, когда произойдёт первое из следующих событий:

  • объект является сборщиком мусора
  • вызывается метод объекта remove() или
  • программа выходит.

Мы можем попытаться реализовать класс с помощью метода __del__() следующим образом:

class TempDir:
    def __init__(self):
        self.name = tempfile.mkdtemp()

    def remove(self):
        if self.name is not None:
            shutil.rmtree(self.name)
            self.name = None

    @property
    def removed(self):
        return self.name is None

    def __del__(self):
        self.remove()

Начиная с Python 3.4, методы __del__() больше не препятствуют сборке мусора ссылочных циклов, а глобальные значения модулей больше не принудительно устанавливаются в None во время выключения интерпретатора. Так что данный код должен без проблем работать на CPython.

Однако обработка методов __del__(), как известно, зависит от реализации, поскольку она зависит от внутренних деталей реализации сборщика мусора интерпретатора.

Более надежной альтернативой может быть определение финализатора, который ссылается только на определённые функции и объекты, которые ему нужны, вместо того, чтобы иметь доступ к полному состоянию объекта:

class TempDir:
    def __init__(self):
        self.name = tempfile.mkdtemp()
        self._finalizer = weakref.finalize(self, shutil.rmtree, self.name)

    def remove(self):
        self._finalizer()

    @property
    def removed(self):
        return not self._finalizer.alive

Определённый таким образом, наш финализатор получает только ссылку на детали, необходимые ему для надлежащей очистки каталога. Если объект никогда не собирает мусор, финализатор все равно будет вызываться при выходе.

Другое преимущество финализаторов на основе weakref заключается в том, что их можно использовать для регистрации финализаторов для классов, определение которых контролируется третьей стороной, например, запуск кода при выгрузке модуля:

import weakref, sys
def unloading_module():
    # неявная ссылка на глобальные переменные модуля из тела функции
weakref.finalize(sys.modules[__name__], unloading_module)

Примечание

Если вы создаёте объект финализатора в потоке демона сразу после выхода из программы, существует вероятность того, что финализатор не будет вызван при выходе. Однако в потоке демона atexit.register(), try: ... finally: ... и with: ... также не гарантируют, что произойдёт очистка.