Рубрики
Без рубрики

weakref – Непостоянные ссылки на объекты

Автор оригинала: Doug Hellmann.

Цель:

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

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

Рекомендации

Слабые ссылки на объекты управляются с помощью класса ref . Чтобы получить исходный объект, вызовите ссылочный объект.

weakref_ref.py

import weakref


class ExpensiveObject:

    def __del__(self):
        print('(Deleting {})'.format(self))


obj  ExpensiveObject()
r  weakref.ref(obj)

print('obj:', obj)
print('ref:', r)
print('r():', r())

print('deleting obj')
del obj
print('r():', r())

В этом случае, поскольку obj удаляется перед вторым вызовом ссылки, ref возвращает None .

$ python3 weakref_ref.py

obj: <__main__.ExpensiveObject object at 0x1007b1a58>
ref: 
r(): <__main__.ExpensiveObject object at 0x1007b1a58>
deleting obj
(Deleting <__main__.ExpensiveObject object at 0x1007b1a58>)
r(): None

Ссылочные обратные вызовы

Конструктор ref принимает дополнительную функцию обратного вызова, которая вызывается при удалении указанного объекта.

weakref_ref_callback.py

import weakref


class ExpensiveObject:

    def __del__(self):
        print('(Deleting {})'.format(self))


def callback(reference):
    """Invoked when referenced object is deleted"""
    print('callback({!r})'.format(reference))


obj  ExpensiveObject()
r  weakref.ref(obj, callback)

print('obj:', obj)
print('ref:', r)
print('r():', r())

print('deleting obj')
del obj
print('r():', r())

Обратный вызов получает объект ссылки в качестве аргумента после того, как ссылка «мертва» и больше не ссылается на исходный объект. Одно из применений этой функции – удаление слабого ссылочного объекта из кеша.

$ python3 weakref_ref_callback.py

obj: <__main__.ExpensiveObject object at 0x1010b1978>
ref: 
r(): <__main__.ExpensiveObject object at 0x1010b1978>
deleting obj
(Deleting <__main__.ExpensiveObject object at 0x1010b1978>)
callback()
r(): None

Завершение объектов

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

weakref_finalize.py

import weakref


class ExpensiveObject:

    def __del__(self):
        print('(Deleting {})'.format(self))


def on_finalize(*args):
    print('on_finalize({!r})'.format(args))


obj  ExpensiveObject()
weakref.finalize(obj, on_finalize, 'extra argument')

del obj

Аргументы для finalize – это отслеживаемый объект, вызываемый объект, вызываемый при сборке мусора, и любые позиционные или именованные аргументы, передаваемые вызываемому объекту.

$ python3 weakref_finalize.py

(Deleting <__main__.ExpensiveObject object at 0x1019b10f0>)
on_finalize(('extra argument',))

Экземпляр finalize имеет свойство с возможностью записи atexit для управления вызовом обратного вызова при выходе из программы, если он еще не был вызван.

weakref_finalize_atexit.py

import sys
import weakref


class ExpensiveObject:

    def __del__(self):
        print('(Deleting {})'.format(self))


def on_finalize(*args):
    print('on_finalize({!r})'.format(args))


obj  ExpensiveObject()
f  weakref.finalize(obj, on_finalize, 'extra argument')
f.atexit  bool(int(sys.argv[1]))

По умолчанию вызывается обратный вызов. Установка для atexit значения false отключает такое поведение.

$ python3 weakref_finalize_atexit.py 1

on_finalize(('extra argument',))
(Deleting <__main__.ExpensiveObject object at 0x1007b10f0>)

$ python3 weakref_finalize_atexit.py 0

Предоставление экземпляру finalize ссылки на отслеживаемый им объект приводит к тому, что ссылка сохраняется, поэтому объект никогда не будет сборщиком мусора.

weakref_finalize_reference.py

import gc
import weakref


class ExpensiveObject:

    def __del__(self):
        print('(Deleting {})'.format(self))


def on_finalize(*args):
    print('on_finalize({!r})'.format(args))


obj  ExpensiveObject()
obj_id  id(obj)

f  weakref.finalize(obj, on_finalize, obj)
f.atexit  False

del obj

for o in gc.get_objects():
    if id(o)  obj_id:
        print('found uncollected object in gc')

Как показывает этот пример, даже несмотря на то, что явная ссылка на obj удалена, объект сохраняется и становится видимым для сборщика мусора через f .

$ python3 weakref_finalize_reference.py

found uncollected object in gc

Использование связанного метода отслеживаемого объекта в качестве вызываемого также может помешать правильной финализации объекта.

weakref_finalize_reference_method.py

import gc
import weakref


class ExpensiveObject:

    def __del__(self):
        print('(Deleting {})'.format(self))

    def do_finalize(self):
        print('do_finalize')


obj  ExpensiveObject()
obj_id  id(obj)

f  weakref.finalize(obj, obj.do_finalize)
f.atexit  False

del obj

for o in gc.get_objects():
    if id(o)  obj_id:
        print('found uncollected object in gc')

Поскольку вызываемый объект, переданный в finalize , является связанным методом экземпляра obj , объект finalize содержит ссылку на obj , который нельзя удалить и сбор мусора.

$ python3 weakref_finalize_reference_method.py

found uncollected object in gc

Прокси

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

weakref_proxy.py

import weakref


class ExpensiveObject:

    def __init__(self, name):
        self.name  name

    def __del__(self):
        print('(Deleting {})'.format(self))


obj  ExpensiveObject('My Object')
r  weakref.ref(obj)
p  weakref.proxy(obj)

print('via obj:', obj.name)
print('via ref:', r().name)
print('via proxy:', p.name)
del obj
print('via proxy:', p.name)

Если доступ к прокси-серверу осуществляется после удаления референтного объекта, возникает исключение ReferenceError .

$ python3 weakref_proxy.py

via obj: My Object
via ref: My Object
via proxy: My Object
(Deleting <__main__.ExpensiveObject object at 0x1007aa7b8>)
Traceback (most recent call last):
  File "weakref_proxy.py", line 30, in 
    print('via proxy:', p.name)
ReferenceError: weakly-referenced object no longer exists

Кэширование объектов

Классы ref и proxy считаются «низкоуровневыми». Классы WeakKeyDictionary и WeakValueDictionary полезны для поддержки слабых ссылок на отдельные объекты и обеспечения возможности циклов для сбора мусора, однако они предоставляют более подходящий API для создания кеша из нескольких объектов.

Класс WeakValueDictionary использует слабые ссылки на хранимые в нем значения, что позволяет им собирать мусор, когда другой код на самом деле их не использует. Использование явных вызовов сборщика мусора демонстрирует разницу между обработкой памяти с помощью обычного словаря и WeakValueDictionary :

weakref_valuedict.py

import gc
from pprint import pprint
import weakref

gc.set_debug(gc.DEBUG_UNCOLLECTABLE)


class ExpensiveObject:

    def __init__(self, name):
        self.name  name

    def __repr__(self):
        return 'ExpensiveObject({})'.format(self.name)

    def __del__(self):
        print('    (Deleting {})'.format(self))


def demo(cache_factory):
    # hold objects so any weak references
    # are not removed immediately
    all_refs  {}
    # create the cache using the factory
    print('CACHE TYPE:', cache_factory)
    cache  cache_factory()
    for name in ['one', 'two', 'three']:
        o  ExpensiveObject(name)
        cache[name]  o
        all_refs[name]  o
        del o  # decref

    print('  all_refs =', end' ')
    pprint(all_refs)
    print('\n  Before, cache contains:', list(cache.keys()))
    for name, value in cache.items():
        print('    {} = {}'.format(name, value))
        del value  # decref

    # remove all references to the objects except the cache
    print('\n  Cleanup:')
    del all_refs
    gc.collect()

    print('\n  After, cache contains:', list(cache.keys()))
    for name, value in cache.items():
        print('    {} = {}'.format(name, value))
    print('  demo returning')
    return


demo(dict)
print()

demo(weakref.WeakValueDictionary)

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

$ python3 weakref_valuedict.py

CACHE TYPE: 
  all_refs = {'one': ExpensiveObject(one),
 'three': ExpensiveObject(three),
 'two': ExpensiveObject(two)}

  Before, cache contains: ['one', 'three', 'two']
    one = ExpensiveObject(one)
    three = ExpensiveObject(three)
    two = ExpensiveObject(two)

  Cleanup:

  After, cache contains: ['one', 'three', 'two']
    one = ExpensiveObject(one)
    three = ExpensiveObject(three)
    two = ExpensiveObject(two)
  demo returning
    (Deleting ExpensiveObject(one))
    (Deleting ExpensiveObject(three))
    (Deleting ExpensiveObject(two))

CACHE TYPE: 
  all_refs = {'one': ExpensiveObject(one),
 'three': ExpensiveObject(three),
 'two': ExpensiveObject(two)}

  Before, cache contains: ['one', 'three', 'two']
    one = ExpensiveObject(one)
    three = ExpensiveObject(three)
    two = ExpensiveObject(two)

  Cleanup:
    (Deleting ExpensiveObject(one))
    (Deleting ExpensiveObject(three))
    (Deleting ExpensiveObject(two))

  After, cache contains: []
  demo returning

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

Предупреждение

В документации библиотеки для weakref содержится это предупреждение:

Внимание: потому что

WeakValueDictionary

построен на вершине Словарь Python, он не должен изменять размер при повторении Это. Это может быть сложно обеспечить

WeakValueDictionary

потому что действия, выполняемые программой во время итерации может привести к исчезновению элементов в словаре “на магия »(как побочный эффект сборки мусора).

Смотрите также