События являются распространенным сценарием для слабых ссылок.
Проблема
Рассмотрим пару объектов: излучатель и приемник. Приемник имеет более короткий срок службы, чем излучатель.
Вы можете попробовать реализацию, как это:
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
Однако, в этом случае, Эмитент сохраняет ссылку на связанный метод callback
, который держит ссылку на приемник. Таким образом, излучатель сохраняет приемник в живых:
# ...continued...
del l
e.emit() # Message received: hello
Это иногда хлопотно. Представьте себе, что Emitter
является частью некоторой модели данных, которая указывает, когда изменения данных и Receiver
было создано диалоговым окном, которое слушает об изменениях для обновления некоторых элементов управления пользовательского интерфейса.
В течение срока службы приложения могут быть созданы многочисленные диалоги, и мы не хотим, чтобы их приемники все еще регистрировались внутри излучателя задолго после того, как окно было закрыто. Это будет утечка памяти.
Удаление обратных вызовов вручную - это один из вариантов (как хлопотный), использование слабых ссылок - другое.
Решение
Там хороший класс 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.
Хмм ... Я не понимал, что вы упомянули о кешах ... Полагаю, я должен удалить свой ответ и внимательно прочитать в следующий раз :-). – Tom
Ну, это было всего лишь однострочное упоминание о тайниках. Затем я расширил ответ, * после * вы отправили свои сообщения :) –
Хорошо, ну, по крайней мере, я не сумасшедший :-). Он не говорит, что ваше сообщение было отредактировано. В любом случае. Мне понравился ваш ответ после того, как я прочитал его снова ... и я не хочу оторваться от голода: -). – Tom