python

«Слабые» ссылки в CPython

  • четверг, 6 января 2022 г. в 00:47:45
https://habr.com/ru/post/599411/
  • Python


Модуль weakref позволяет создавать "слабые" ссылки на объекты.

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

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

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

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

Finalize предоставляет простой путь зарегистрировать cleanup функцию, которая вызывается, когда объект удаляется. Это проще, чем установить callback функцию на "слабую" ссылку, поскольку модуль автоматически гарантирует, что finalizer не будет удалён до того, как будет удалён объект.

Для большинства программ использование этих типов контейнеров и finalize - это всё, что нужно. Обычно не нужно создавать "слабые" ссылки вручную. Низкоуровневый механизм предоставлен для расширенного использования.

Не на все объекты могут ссылаться слабые ссылки. Среди объектов, на которые можно ссылаться, есть экземпляры классов, функции (кроме тех, которые написаны с использованием C расшерения), методы объектов, множества, frozensets, некоторые объекты файлов, генераторы, типы объектов, сокеты, arrays, deques, регулярные выражения, и code objects. В версии CPython 3.2 добавлена поддержка для thread.lock, thread.Lock и code objects.

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

class Dict(dict): pass

obj = Dict(red=1, green=2, blue=3)

Некоторые другие встроенные типы, такие как tuple и int не поддерживают "слабые" ссылки, даже их подкласы.

Каждый объект, на который могут ссылаться "слабые" ссылки, содержит атрибут _ _ weakref _ _, который является "слабой" ссылкой (или "слабым" прокси-объектом) и так же является первым объектов в двухсвязном списке всех "слабых" ссылок и "слабых" прокси-объектов.

Когда в данном типе присутствует атрибут _ _ slots _ _, "слабые" сылки не доступны до тех пор, пока атрибут _ _ weakref _ _ явно не присутствует в списке _ _ slots _ _.

class weakref.ref(object[, callback])

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

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

Исключения, выброшенные в callback, будут выведены в стандартный поток ошибок, но не будут проброшены выше; они обрабатываются точно так же как исключения, выброшенные в методе _ _ del _ _.

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

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

_ _ callback _ _

В версии CPython 3.4 появился атрибут _ _ callback _ _ (только для чтения). Он возвращает функцию callback, относящуюся к данной "слабой" ссылке. Если он не был установлен или оригинальный объект удалён, то атрибут вернёт None.

weakref.proxy(object[, callback])

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

weakref.getweakrefcount(object)

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

weakref.getweakrefs(object)

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

class weakref.WeakKeyDictionary([dict])

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

WeakKeyDictionary.keyrefs()

Возвращает итерируемый объект "слабых" ссылок на ключ.

class weakref.WeakValueDictionary([dict])

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

WeakValueDictionary.valuerefs()

Возвращает итерируемый объект "слабых" ссылок на значение.

class weakref.WeakSet([elements])

Класс множества, который содержит "слабые" ссылки на свои элементы.

class weakref.WeakMethod(method)

В версии CPython 3.4 для ref добавлен подкласс, который иммитирует "слабую" ссылку на bound метод. Поскольку bound метод является недолговечным, обычная "слабая" ссылка здесь не подходит. WeakMethod содержит специальный код, который воссоздаёт bound метод до тех пор, пока объект или исходная функция не "умрут".

>>> 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()
>>>

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

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

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

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

_ _ call _ _()

Если self "жив", то отмечает finalizer "умершим" и возвращает результат вызова func(*args, **kwargs). Если self "умер" - возвращает None.

detach()

Если self "жив", то отмечает finalizer "умершим" и возвращает кортеж (obj, func, args, kwargs). Если self "умер" - возвращает None.

peek()

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

alive

Свойство, которое возвращает True, если finalizer "жив", иначе - False.

atexit

Изменяемое свойство, которое по-умолчанию True. Когда программа завершается, она вызывает оставшиеся finalizers, у которых atexit установлено в True.

Finalizer

Главное преимущество использования finalize заключается в том, что не нужно следить за тем, чтобы finalizer оставался "живым". Например:

>>> 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!

finalizer может быть вызван явно, но только единожды.

>>> 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

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

>>> 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, то finalizer будет вызван при завершении программы, если объект ещё жив.

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