Модуль 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

Комментарии (7)


  1. masai
    05.01.2022 19:04
    +3

    Укажите, пожалуйста, что это перевод https://docs.python.org/3/library/weakref.html


  1. Coriolis
    05.01.2022 19:19

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

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


    1. masai
      05.01.2022 19:25
      +1

      Да, это ссылки, которые не держат объекты.

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


      1. yrustt Автор
        05.01.2022 19:31

        Указал, что статья - перевод.


      1. tetelevm
        06.01.2022 16:42

        Про сборщик мусора - не совсем. Основной сборщик мусора в CPython (в отличие от PyPy) как раз и основан на подсчёте ссылок, а `gc.collect()`, если не ошибаюсь, должен вызываться вручную. И он не ищет циклы, а наоборот, ищет то, что доступно из верхней области видимости, а остальное удаляет.


        1. masai
          06.01.2022 18:46

          В CPython (по умолчанию обычно он подразумевается, конечно) работают оба механизма одновременно:

          • подсчёт ссылок (но я бы не называл это сборщиком мусора, так как тут сборщика как такового нет, объекты и ссылки сами справляются)

          • и собственно сборщик мусора, основанный на поколениях.

          Задача сборщика мусора — именно поиск циклических ссылок на объекты. Собственно, было бы странно, если бы он не искал циклы, так как с нециклическими зависимостями и подсчёт может справится.

          Сборщик мусора работает автоматически. Управлять частотой вызова сборщика можно задав пороги для каждого поколения с помощью функции gc.set_threshold(...).

          gc.collect() принудительно запускает сборщик. Как правило, вручную вызывать эту функцию не стоит, так как сборщик и так вызывается достаточно часто, а ручные вызовы могут только замедлить программу.


    1. yrustt Автор
      05.01.2022 19:32

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