События - общий сценарий для слабых ссылок.
Проблема
Рассмотрим пару объектов: излучатель и приемник. Ресивер имеет более короткий срок службы, чем излучатель.
Вы можете попробовать реализацию, подобную этой:
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
, который сохраняет ссылку на Receiver. Таким образом, Эмитент поддерживает жизнь Приемника:
# ...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
) теперь осиротел - ни излучатель, ни получатель не имеют к нему строгой ссылки. Отсюда сразу мусор.
Давайте сделаем так, чтобы Receiver (а не Emitter) сохранял строгую ссылку на этот обратный вызов:
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.