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

Источник: Lib/weakref.py


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

В дальнейшем термин referent означает объект, на который ссылается слабая ссылка.

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

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

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

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

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

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

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

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

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

class Dict(dict):
    pass

obj = Dict(red=1, green=2, blue=3)   # this object is weak referenceable

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

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

Когда для данного типа определены __slots__, поддержка слабых ссылок отключена, если только строка '__weakref__' не присутствует в последовательности строк в объявлении __slots__. Подробности см. в разделе __slots__ documentation.

class weakref.ref(object[, callback])

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

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

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

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

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

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

__callback__

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

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

weakref.proxy(object[, callback])

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

Доступ к атрибуту прокси-объекта после того, как референт был собран, вызывает ReferenceError.

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

weakref.getweakrefcount(object)

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

weakref.getweakrefs(object)

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

class weakref.WeakKeyDictionary([dict])

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

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

>>> class T(str): pass
...
>>> k1, k2 = T(), T()
>>> d = weakref.WeakKeyDictionary()
>>> d[k1] = 1   # d = {k1: 1}
>>> d[k2] = 2   # d = {k1: 2}
>>> del k1      # d = {}

Обходным путем может быть удаление ключа перед переназначением:

>>> class T(str): pass
...
>>> k1, k2 = T(), T()
>>> d = weakref.WeakKeyDictionary()
>>> d[k1] = 1   # d = {k1: 1}
>>> del d[k1]
>>> d[k2] = 2   # d = {k2: 2}
>>> del k1      # d = {k2: 2}

Изменено в версии 3.9: Добавлена поддержка операторов | и |=, указанных в PEP 584.

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

WeakKeyDictionary.keyrefs()

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

class weakref.WeakValueDictionary([dict])

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

Изменено в версии 3.9: Добавлена поддержка операторов | и |=, как указано в PEP 584.

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

WeakValueDictionary.valuerefs()

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

class weakref.WeakSet([elements])

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

class weakref.WeakMethod(method[, callback])

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

>>> class C:
...     def method(self):
...         print("method called!")
...
>>> 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()()
method called!
>>> del c
>>> gc.collect()
0
>>> r()
>>>

callback - это то же самое, что и одноименный параметр функции ref().

Added in version 3.4.

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

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

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

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

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

Финализатор никогда не будет вызывать свой обратный вызов во время поздней части interpreter shutdown, когда глобальные файлы модуля могут быть заменены на 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

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

atexit

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

Примечание

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

Added in version 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 is a weak reference object
o = r()
if o is None:
    # referent has been garbage collected
    print("Object has been deallocated; can't frobnicate.")
else:
    print("Object is still live!")
    o.do_something_useful()

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

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

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

import weakref

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

    def __call__(self):
        """Return a pair containing the referent and the number of
        times the reference has been called.
        """
        ob = super().__call__()
        if ob is not None:
            self.__counter += 1
            ob = (ob, self.__counter)
        return ob

Пример

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

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!")  
<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()                     # callback not called because finalizer dead
>>> del obj                 # callback not called because finalizer dead

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

>>> obj = Object()
>>> f = weakref.finalize(obj, callback, 1, 2, z=3)
>>> f.detach()                                           
(<...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 во время interpreter shutdown. Так что этот код должен работать без проблем на 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():
    # implicit reference to the module globals from the function body
weakref.finalize(sys.modules[__name__], unloading_module)

Примечание

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