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 implementation detail: Другие встроенные типы, такие как tuple и int, не поддерживают слабые ссылки, даже если они являются подклассами.

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

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

class weakref.ref(object[, callback])

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

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

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

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

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

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

__callback__

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

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

weakref.proxy(object[, callback])

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

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

weakref.getweakrefcount(object)

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

weakref.getweakrefs(object)

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

class weakref.WeakKeyDictionary([dict])

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

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

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

WeakKeyDictionary.keyrefs()

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

class weakref.WeakValueDictionary([dict])

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

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

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

WeakValueDictionary.valuerefs()

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

class weakref.WeakSet([elements])

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

class weakref.WeakMethod(method)

Пользовательский подкласс 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()
>>>

Добавлено в версии 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.

Добавлено в версии 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: ... также не гарантируют, что очистка произойдет.

Вернуться на верх