Автор оригинала: 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, inprint('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
потому что действия, выполняемые программой во время итерации может привести к исчезновению элементов в словаре “на магия »(как побочный эффект сборки мусора).
Смотрите также
- стандартная библиотечная документация для weakref
- gc – модуль
gc
является интерфейсом для сборщика мусора интерпретатора. - PEP 205 – предложение по расширению “слабых ссылок”.