Когда использовать слабые ссылки в Python?
-
19-09-2019 - |
Вопрос
Может ли кто-нибудь объяснить использование слабых ссылок?
А документация не объясняет это точно, он просто говорит, что сборщик мусора может в любое время уничтожить объект, связанный с помощью слабой ссылки.Тогда какой смысл иметь объект, который может исчезнуть в любой момент?Что делать, если мне нужно использовать его сразу после его исчезновения?
Можете ли вы объяснить их на хороших примерах?
Спасибо
Решение
Типичное использование слабых ссылок заключается в том, что A имеет ссылку на B, а B имеет ссылку на A.Без надлежащего сборщика мусора, обнаруживающего циклы, эти два объекта никогда не получат GC, даже если ни на один из них нет ссылок «извне».Однако если одна из ссылок «слабая», объекты будут правильно GC'd.
Однако Питон делает иметь сборщик мусора, определяющий цикличность (начиная с версии 2.0!), так что это не в счет :)
Другое применение слабых ссылок — для кэшей.Это упомянуто в weakref
документация:
Основное использование слабых ссылок — реализация кэшей или отображений, содержащих большие объекты, где желательно, чтобы большой объект не оставался активным только потому, что он появляется в кэше или сопоставлении.
Если GC решит уничтожить один из этих объектов, и вам это нужно, вы можете просто пересчитать/перезагрузить данные.
Другие советы
События — это распространенный сценарий для слабых ссылок.
Проблема
Рассмотрим пару объектов:Излучатель и приемник.Срок службы приемника короче, чем у излучателя.
Вы можете попробовать такую реализацию:
class Emitter(object):
def __init__(self):
self.listeners = set()
def emit(self):
for listener in self.listeners:
# Notify
listener('hello')
class Receiver(object):
def __init__(self, emitter):
emitter.listeners.add(self.callback)
def callback(self, msg):
print 'Message received:', msg
e = Emitter()
l = Receiver(e)
e.emit() # Message received: hello
Однако в этом случае Emitter сохраняет ссылку на связанный метод. callback
который сохраняет ссылку на получателя.Таким образом, излучатель поддерживает работу приемника:
# ...continued...
del l
e.emit() # Message received: hello
Иногда это вызывает затруднения.Представь это Emitter
является частью некоторой модели данных, которая указывает, когда данные изменяются и Receiver
был создан диалоговым окном, которое прослушивает эти изменения для обновления некоторых элементов управления пользовательского интерфейса.
В течение всего времени существования приложения может быть создано несколько диалоговых окон, и мы не хотим, чтобы их получатели все еще были зарегистрированы внутри Emitter еще долгое время после закрытия окна.Это будет утечка памяти.
Удаление обратных вызовов вручную — один из вариантов (не менее затруднительный), а использование слабых ссылок — другой.
Решение
Есть хороший класс WeakSet
он выглядит как обычный набор, но сохраняет свои члены, используя слабые ссылки, и больше не сохраняет их после освобождения.
Отличный!Давайте использовать это:
def __init__(self):
self.listeners = weakref.WeakSet()
и запустите снова:
e = Emitter()
l = Receiver(e)
e.emit()
del l
e.emit()
Ой, вообще ничего не происходит!Это потому, что связанный метод (конкретный получатель callback
) теперь осиротел — ни излучатель, ни приемник не имеют на него сильной ссылки.Следовательно, мусор собирается немедленно.
Давайте сделаем так, чтобы приемник (на этот раз не эмиттер) сохранял строгую ссылку на этот обратный вызов:
class Receiver(object):
def __init__(self, emitter):
# Create the bound method object
cb = self.callback
# Register it
emitter.listeners.add(cb)
# But also create an own strong reference to keep it alive
self._callbacks = set([cb])
Теперь мы можем наблюдать ожидаемое поведение:Эмиттер сохраняет обратный вызов только до тех пор, пока жив Получатель.
e = Emitter()
l = Receiver(e)
assert len(e.listeners) == 1
del l
import gc; gc.collect()
assert len(e.listeners) == 0
Под капотом
Обратите внимание, что мне пришлось поставить gc.collect()
здесь, чтобы убедиться, что приемник действительно немедленно очищен.Здесь это нужно, потому что сейчас идет цикл сильных ссылок:связанный метод относится к получателю и наоборот.
Это не так уж и плохо;это означает лишь то, что очистка получателя будет отложена до следующего запуска сборщика мусора.Циклические ссылки не могут быть очищены с помощью простого механизма подсчета ссылок.
Если вы действительно хотите, вы можете удалить цикл сильных ссылок, заменив связанный метод пользовательским объектом функции, который сохранит его self
как слабая ссылка тоже.
def __init__(self, emitter):
# Create the bound method object
weakself = weakref.ref(self)
def cb(msg):
self = weakself()
self.callback(msg)
# Register it
emitter.listeners.add(cb)
# But also create an own strong reference to keep it alive
self._callbacks = set([cb])
Давайте поместим эту логику во вспомогательную функцию:
def weak_bind(instancemethod):
weakref_self = weakref.ref(instancemethod.im_self)
func = instancemethod.im_func
def callback(*args, **kwargs):
self = weakref_self()
bound = func.__get__(self)
return bound(*args, **kwargs)
return callback
class Receiver(object):
def __init__(self, emitter):
cb = weak_bind(self.callback)
# Register it
emitter.listeners.add(cb)
# But also create an own strong reference to keep it alive
self._callbacks = set([cb])
Теперь нет цикла сильных ссылок, поэтому, когда Receiver
освобождена, функция обратного вызова также будет освобождена (и удалена из эмиттера). WeakSet
) сразу, без необходимости полного цикла GC.
- Слабые ссылки являются важной концепцией в Python, которая отсутствует в языках, как Java (Java 1.5).
В схеме дизайна наблюдателя обычно наблюдаемый объект должен поддерживать слабые ссылки на объект наблюдателя.
например.A генерирует событие done(), а B регистрируется в A, чтобы Прослушайте событие done().Таким образом, всякий раз, когда генерируется done(), B имеет значение Уведомления.Но если B не требуется в приложении, то и A не должен становятся помехой при сборке мусора в A (так как A удерживают ссылка на Б).Таким образом, если А имеет слабую связь с В, и когда все ссылки на A удалены, то B будет удален сборщиком мусора.
- Это также очень полезно при реализации кэшей.