Когда использовать слабые ссылки в Python? - PullRequest
35 голосов
/ 13 марта 2010

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

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

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

Спасибо

Ответы [ 3 ]

34 голосов
/ 17 февраля 2013

События - общий сценарий для слабых ссылок.


Проблема

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

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

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.

25 голосов
/ 13 марта 2010

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

Тем не менее, Python имеет имеет сборщик мусора, обнаруживающий цикл (начиная с 2.0!), Поэтому он не считается:)

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

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

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

2 голосов
/ 30 октября 2016
  • Слабые ссылки - важная концепция в Python, которая отсутствует на языках нравится Java (Java 1.5).
  • В шаблоне проектирования Observer обычно наблюдаемый объект должен поддерживать слабые ссылки на объект Observer.

    например. A испускает событие done (), а B регистрируется в A, который хочет прослушать событие сделано (). Таким образом, всякий раз, когда done () испускается, B уведомление. Но если B не требуется в приложении, то A не должен стать препятствием в сборке мусора в А (так как А держать ссылка на Б). Таким образом, если A имеет слабую ссылку на B, и когда все ссылки на A удалены, тогда B будет сборщиком мусора.

  • Это также очень полезно при реализации кэшей.
...