weakref — Слабкі посилання

Вихідний код: Lib/weakref.py


Модуль weakref дозволяє програмісту Python створювати слабкі посилання на об’єкти.

Далі термін referent означає об’єкт, на який посилається слабке посилання.

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

Основним використанням слабких посилань є реалізація кешів або відображень, що містять великі об’єкти, де бажано, щоб великий об’єкт не зберігався живим лише тому, що він з’являється в кеші чи відображенні.

Наприклад, якщо у вас є кілька великих бінарних об’єктів зображення, ви можете призначити ім’я кожному. Якби ви використовували словник Python для зіставлення імен із зображеннями або зображень з іменами, об’єкти зображення залишалися б живими лише тому, що вони відображалися як значення або ключі в словниках. Класи WeakKeyDictionary і WeakValueDictionary, що надаються модулем weakref, є альтернативою, використовуючи слабкі посилання для створення відображень, які не зберігають об’єкти лише тому, що вони з’являються в об’єктах відображення . Якщо, наприклад, об’єкт зображення є значенням у WeakValueDictionary, тоді, коли останні посилання на цей об’єкт зображення є слабкими посиланнями, утримуваними слабкими відображеннями, збір сміття може відновити об’єкт і його відповідні записи у слабких відображеннях просто видаляються.

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

finalize забезпечує прямий спосіб реєстрації функції очищення, яка буде викликатися під час збирання сміття об’єкта. Це простіше у використанні, ніж налаштування функції зворотного виклику для необробленого слабкого посилання, оскільки модуль автоматично гарантує, що фіналізатор залишається активним, доки об’єкт не буде зібрано.

Більшість програм має виявити, що використання одного з цих слабких типів контейнерів або finalize — це все, що їм потрібно - зазвичай не потрібно безпосередньо створювати власні слабкі посилання. Механізм низького рівня розкривається модулем weakref для розширеного використання.

Not all objects can be weakly referenced. Objects which support weak references include class instances, functions written in Python (but not in C), instance methods, sets, frozensets, some file objects, generators, type objects, sockets, arrays, deques, regular expression pattern objects, and code objects.

Змінено в версії 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__ документацію для деталей.

class weakref.ref(object[, callback])

Повернути слабке посилання на об’єкт. Вихідний об’єкт можна отримати, викликавши еталонний об’єкт, якщо референт ще живий; якщо референт більше не живий, виклик об’єкта посилання призведе до повернення None. Якщо надано callback, а не None, і повернутий об’єкт weakref все ще живий, зворотний виклик буде викликано, коли об’єкт буде завершено; об’єкт слабкого посилання буде передано як єдиний параметр зворотного виклику; референт більше не буде доступний.

Для одного об’єкта допускається створення багатьох слабких посилань. Зворотні виклики, зареєстровані для кожного слабкого посилання, викликатимуться від останнього зареєстрованого зворотного виклику до найстарішого зареєстрованого зворотного виклику.

Exceptions raised by the callback will be noted on the standard error output, but cannot be propagated; they are handled in exactly the same way as exceptions raised from an object’s __del__() method.

Слабкі посилання є hashable, якщо об’єкт є хешованим. Вони збережуть своє хеш-значення навіть після видалення об’єкта. Якщо hash() викликається вперше лише після того, як об’єкт було видалено, виклик викличе TypeError.

Слабкі посилання підтримують тести на рівність, але не впорядкування. Якщо референти ще живі, два посилання мають таке ж відношення рівності, що й їхні референти (незалежно від зворотного виклику). Якщо будь-який референт було видалено, посилання є рівними, лише якщо об’єкти еталонів є одним і тим же об’єктом.

Це тип підкласу, а не фабрична функція.

__callback__

Цей атрибут лише для читання повертає зворотний виклик, наразі пов’язаний зі слабким посиланням. Якщо зворотного виклику немає або референт слабкого посилання більше не живий, цей атрибут матиме значення None.

Змінено в версії 3.4: Додано атрибут __callback__.

weakref.proxy(object[, callback])

Return a proxy to object which uses a weak reference. This supports use of the proxy in most contexts instead of requiring the explicit dereferencing used with weak reference objects. The returned object will have a type of either ProxyType or CallableProxyType, depending on whether object is callable. Proxy objects are not hashable regardless of the referent; this avoids a number of problems related to their fundamentally mutable nature, and prevents their use as dictionary keys. callback is the same as the parameter of the same name to the ref() function.

Accessing an attribute of the proxy object after the referent is garbage collected raises ReferenceError.

Змінено в версії 3.8: Розширено підтримку операторів на проксі-об’єктах, щоб включити оператори множення матриці @ і @=.

weakref.getweakrefcount(object)

Повертає кількість слабких посилань і проксі, які посилаються на об’єкт.

weakref.getweakrefs(object)

Повертає список усіх слабких посилань і проксі-об’єктів, які посилаються на об’єкт.

class weakref.WeakKeyDictionary([dict])

Клас зіставлення, який слабко посилається на ключі. Записи в словнику буде відкинуто, коли більше немає сильного посилання на ключ. Це можна використовувати для зв’язування додаткових даних з об’єктом, що належить іншим частинам програми, без додавання атрибутів до цих об’єктів. Це може бути особливо корисним для об’єктів, які перевизначають доступ до атрибутів.

Note that when a key with equal value to an existing key (but not equal identity) is inserted into the dictionary, it replaces the value but does not replace the existing key. Due to this, when the reference to the original key is deleted, it also deletes the entry in the dictionary:

>>> 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 = {}

A workaround would be to remove the key prior to reassignment:

>>> 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 objects have an additional method that has the same issues as the WeakKeyDictionary.keyrefs() method.

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 is the same as the parameter of the same name to the ref() function.

Added in version 3.4.

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

Повертає викликаний об’єкт фіналізатора, який буде викликаний, коли obj буде зібрано сміття. На відміну від звичайного слабкого посилання, фіналізатор завжди житиме, доки не буде зібрано еталонний об’єкт, що значно спрощує керування життєвим циклом.

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

Exceptions raised by finalizer callbacks during garbage collection will be shown on the standard error output, but cannot be propagated. They are handled in the same way as exceptions raised from an object’s __del__() method or a weak reference’s callback.

Коли програма завершує роботу, викликається кожен живий фіналізатор, що залишився, якщо його атрибут 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

Властивість, яка є істинною, якщо фіналізатор активний, і хибною в іншому випадку.

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

Comparing finalizers with __del__() methods

Припустимо, ми хочемо створити клас, екземпляри якого представляють тимчасові каталоги. Каталоги мають бути видалені разом із їхнім вмістом, коли станеться перша з наступних подій:

  • об’єкт вивезено сміття,

  • the object’s remove() method is called, or

  • програма виходить.

We might try to implement the class using a __del__() method as follows:

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

Starting with Python 3.4, __del__() methods no longer prevent reference cycles from being garbage collected, and module globals are no longer forced to None during interpreter shutdown. So this code should work without any issues on CPython.

However, handling of __del__() methods is notoriously implementation specific, since it depends on internal details of the interpreter’s garbage collector implementation.

Більш надійною альтернативою може бути визначення фіналізатора, який посилається лише на певні функції та об’єкти, які йому потрібні, а не має доступу до повного стану об’єкта:

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

Визначений таким чином, наш фіналізатор отримує лише посилання на деталі, необхідні для належного очищення каталогу. Якщо об’єкт ніколи не отримує сміття, фіналізатор все одно буде викликаний при виході.

Інша перевага фіналізаторів на основі слабких рефлексів полягає в тому, що їх можна використовувати для реєстрації фіналізаторів для класів, де визначення контролює третя сторона, наприклад, запуск коду, коли модуль вивантажується:

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: ... також не гарантують виконання очищення.