2010-03-12 2 views
31

Может ли кто-нибудь объяснить использование слабых ссылок?Когда использовать слабые ссылки в Python?

documentation не объясняет это точно, он просто говорит, что GC может уничтожить объект, связанный с помощью слабой ссылки в любое время. Тогда в чем смысл иметь объект, который может исчезнуть в любое время? Что делать, если мне нужно использовать его сразу после его исчезновения?

Не могли бы вы объяснить им несколько хороших примеров?

Благодаря

ответ

24

Типичное использование слабых ссылок, если А имеет ссылку на B и B имеет ссылку на A. Без надлежащего цикла обнаружения сборщика мусора, эти два объекта никогда бы не GC'd даже если нет ссылок ни на «снаружи». Однако, если одна из ссылок «слабая», объекты получат должным образом GC'd.

Однако, Python делает есть цикл обнаружения сборщика мусора (начиная с версии 2.0!), Так что не в счет :)

Другое использование слабых ссылок для кэшей. Он упоминается в документации weakref:

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

Если GC решает уничтожить один из этих объектов, и вам это нужно, вы можете просто пересчитать/восстановить данные.

+1

Хмм ... Я не понимал, что вы упомянули о кешах ... Полагаю, я должен удалить свой ответ и внимательно прочитать в следующий раз :-). – Tom

+1

Ну, это было всего лишь однострочное упоминание о тайниках. Затем я расширил ответ, * после * вы отправили свои сообщения :) –

+0

Хорошо, ну, по крайней мере, я не сумасшедший :-). Он не говорит, что ваше сообщение было отредактировано. В любом случае. Мне понравился ваш ответ после того, как я прочитал его снова ... и я не хочу оторваться от голода: -). – Tom

25

События являются распространенным сценарием для слабых ссылок.


Проблема

Рассмотрим пару объектов: излучатель и приемник. Приемник имеет более короткий срок службы, чем излучатель.

Вы можете попробовать реализацию, как это:

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.

+0

Я не понимаю причины, лежащие в основе Listener._callbacks. Не будет ли поддерживать обратный вызов в течение всего жизненного цикла объекта Listener? В конце концов, это метод для этого объекта. – Xion

+0

Оказывается, это не так. 'Listener.callback' - это функция, но' l.callback' вызывает 'ваш метод.__get __ (l, Listener) ', который возвращает экземпляр' instancemethod', который запоминает его 'self'. Он всегда воссоздается при поиске метода. [Хорошее чтение здесь] (http://users.rcn.com/python/download/Descriptor.htm#functions-and-methods) – Kos

+0

Возможно, вам больше не нужны ссылки на этот преходящий объект 'instancemetiond', но самому объекту «Listener» (стиль Java). – Xion

1
- Weak references is an important concept in python, which is missing 
    in languages likes Java(java 1.5). 
- In Observer design pattern, generally Observable Object must maintain 
    weak references to the Observer object. 

    eg. A emits an event done() and B registers with A that, it want to 
    listen to event done(). Thus, whenever done() is emitted, B is 
    notified. But If B isn't required in application, then A must not 
    become an hinderance in the garbage collection in A(since A hold the 
    reference to B). Thus, if A has hold weak reference to B, and when 
    all the references to A are away, then B will be garbage collected. 
- It's also very useful in implementing caches.