Вопрос

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

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

Можете ли вы объяснить их на хороших примерах?

Спасибо

Это было полезно?

Решение

Типичное использование слабых ссылок заключается в том, что 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 будет удален сборщиком мусора.

  • Это также очень полезно при реализации кэшей.
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top