Содержание страницы
8.8. weakref – Слабые ссылки¶weakref – Weak references
Исходный код: Lib/weakref.py
Модуль weakref позволяет программисту Python создавать слабые
ссылки на объекты.
В дальнейшем под термином referent понимается объект, на который указывает слабая ссылка.
Слабой ссылки на объект недостаточно, чтобы сохранить объект живым: когда единственные оставшиеся ссылки на референт являются слабыми ссылками, сборщик мусора может уничтожить референт и использовать его память для других целей. Однако, пока объект фактически не уничтожен, слабая ссылка может вернуть объект, даже если на него нет сильных ссылок.
Основное применение слабых ссылок – реализация кэшей или отображений, содержащих большие объекты, когда требуется, чтобы большой объект не оставался в живых только потому, что он присутствует в кэше или отображении.
Например, если у вас есть несколько больших бинарных объектов-изображений, вы можете захотеть
связать имя с каждым. Если вы используете словарь 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) # этот объект поддерживает слабые ссылки
Другие встроенные типы, такие как tuple и int, не поддерживают слабые ссылки, даже если они являются подклассами (Это деталь реализации и может отличаться в различных реализациях Python).
Расширяемые типы можно легко научить поддерживать слабые ссылки; см. Поддержка слабых ссылок.
-
class
weakref.ref(object[, callback])¶ Возвращает слабую ссылку на object. Исходный объект можно получить, вызвав объект ссылки, если референт ещё жив; если референт больше не жив, вызов объекта ссылки приведёт к возврату
None. Если колбэк указан и не равенNone, и возвращённый объект weakref всё ещё жив, колбэк будет вызван, когда объект будет близок к финализации; объект слабой ссылки будет передан как единственный параметр колбэку; референт больше не будет доступен.Допускается создание нескольких слабых ссылок на один и тот же объект. Колбэки, зарегистрированные для каждой слабой ссылки, будут вызываться от самого недавно зарегистрированного колбэка до самого старого.
Исключения, возбуждаемые колбэком, будут отмечены в стандартном потоке ошибок, но не могут быть распространены; они обрабатываются точно так же, как исключения, возбуждаемые методом
__del__()объекта.Слабые ссылки являются хешируемыми, если object хешируем. Они сохраняют своё хеш-значение даже после удаления object. Если
hash()вызывается впервые только после удаления object, вызов возбудитTypeError.Слабые ссылки поддерживают проверку на равенство, но не на упорядочивание. Если референты ещё живы, две ссылки имеют то же отношение равенства, что и их референты (независимо от колбэк). Если любой из референтов был удалён, ссылки равны только в том случае, если объекты ссылок являются одним и тем же объектом.
Это тип, который можно наследовать, а не фабричная функция.
-
__callback__¶ Этот атрибут только для чтения возвращает колбэк, в данный момент связанный с weakref. Если колбэка нет или референт weakref больше не жив, то этот атрибут будет иметь значение
None.
Изменено в версии 3.4: Добавлен атрибут
__callback__.-
-
weakref.proxy(object[, callback])¶ Возвращает прокси для объекта, который использует слабую ссылку. Это позволяет использовать прокси в большинстве контекстов без необходимости явного разыменования, как в случае с объектами слабых ссылок. Возвращаемый объект будет иметь тип
ProxyTypeилиCallableProxyTypeв зависимости от того, является ли объект вызываемым. Прокси-объекты не являются хешируемыми независимо от референта; это позволяет избежать ряда проблем, связанных с их принципиально изменчивой природой, и предотвращает их использование в качестве ключей словаря. колбэк – это тот же параметр с таким же именем, что и у функцииref().
-
weakref.getweakrefcount(object)¶ Возвращает количество слабых ссылок и прокси, которые ссылаются на object.
-
weakref.getweakrefs(object)¶ Возвращает список всех объектов слабых ссылок и прокси, которые ссылаются на object.
-
class
weakref.WeakKeyDictionary([dict])¶ Класс отображения, который хранит слабые ссылки на ключи. Записи словаря будут удалены, когда на ключ больше не останется сильных ссылок. Это можно использовать для связывания дополнительных данных с объектом, принадлежащим другим частям приложения, без добавления атрибутов к этим объектам. Это особенно полезно для объектов, которые переопределяют доступ к атрибутам.
Примечание
Предостережение: поскольку
WeakKeyDictionaryпостроен поверх словаря Python, его размер не должен изменяться при итерации по нему. Это может быть трудно гарантировать дляWeakKeyDictionary, поскольку действия, выполняемые программой во время итерации, могут привести к тому, что элементы словаря исчезнут «магическим образом» (как побочный эффект сборки мусора).
WeakKeyDictionary объекты имеют дополнительный метод, который напрямую раскрывает внутренние ссылки. Эти ссылки не гарантированно являются «живыми» на момент использования, поэтому результат вызова ссылок необходимо проверять перед использованием. Это можно использовать, чтобы избежать создания ссылок, которые заставят сборщик мусора удерживать ключи дольше, чем необходимо.
-
WeakKeyDictionary.keyrefs()¶ Возвращает итератор по слабым ссылкам на ключи.
-
class
weakref.WeakValueDictionary([dict])¶ Класс отображения, который хранит слабые ссылки на значения. Записи словаря будут удалены, когда на значение больше не будет сильных ссылок.
Примечание
Предостережение: поскольку
WeakValueDictionaryпостроен поверх словаря Python, его размер не должен изменяться при итерации по нему. Это может быть трудно гарантировать дляWeakValueDictionary, поскольку действия, выполняемые программой во время итерации, могут привести к тому, что элементы словаря исчезнут «магическим образом» (как побочный эффект сборки мусора).
Объекты 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. Они вызываются в порядке, обратном порядку создания.Финализатор никогда не вызовет свой колбэк на позднем этапе завершения интерпретатора, когда глобальные переменные модуля могут быть заменены на
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¶ Последовательность, содержащая все объекты типа для прокси. Это упрощает проверку, является ли объект прокси, без необходимости называть оба типа прокси.
-
exception
weakref.ReferenceError¶ Исключение, возникающее при использовании прокси-объекта, но базовый объект уже был собран. Это то же самое, что и стандартное исключение
ReferenceError.
См. также
- PEP 205 – слабые ссылки
Предложение и обоснование этой возможности, включая ссылки на более ранние реализации и информацию о подобных возможностях в других языках.
8.8.1. Объекты слабых ссылок¶Weak Reference Objects
Объекты слабых ссылок не имеют методов и атрибутов, кроме ref.__callback__. Объект слабой ссылки позволяет получить объект ссылки (referent), если он ещё существует, путём вызова:
>>> 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("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(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
8.8.2. Пример¶Example
Этот простой пример показывает, как приложение может использовать идентификаторы объектов для получения объектов, которые оно уже видело. Идентификаторы объектов затем можно использовать в других структурах данных, не заставляя объекты оставаться живыми, но объекты всё ещё можно получить по идентификатору, если они живы.
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]
8.8.3. Объекты финализаторов¶Finalizer Objects
Основное преимущество использования 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() # колбэк не вызван, так как финализатор неактивен
>>> del obj # колбэк не вызван, так как финализатор неактивен
Можно отменить регистрацию финализатора с помощью его метода detach(). Это уничтожает финализатор и возвращает аргументы, переданные конструктору при его создании.
>>> obj = Object()
>>> f = weakref.finalize(obj, callback, 1, 2, z=3)
>>> f.detach()
(<__main__.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
8.8.4. Сравнение финализаторов с методами __del__()¶Comparing finalizers with __del__() methods
Предположим, мы хотим создать класс, экземпляры которого представляют временные каталоги. Каталоги должны удаляться вместе с их содержимым при наступлении первого из следующих событий:
объект удаляется сборщиком мусора,
вызывается метод
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: ...
также не гарантируют очистку.