Циркулярная ссылка - перерыв на одну ссылку - PullRequest
0 голосов
/ 12 июня 2018

TL; DR Есть ли способ создать слабую ссылку, которая будет вызывать обратный вызов при наличии 1 сильной ссылки вместо 0?


Для тех, кто считает, что этопроблема XY, вот длинное объяснение:

У меня довольно сложная проблема, которую я пытаюсь решить с помощью своего кода.

Предположим, у нас есть экземпляр некоторого класса Foo идругой класс Bar, который ссылается на экземпляр в том виде, в котором он используется:

class Foo:  # Can be anything
    pass

class Bar:
    """I must hold the instance in order to do stuff"""
    def __init__(self, inst):
        self.inst = inst

foo_to_bar = {}
def get_bar(foo):
    """Creates Bar if one doesn't exist"""
    return foo_to_bar.setdefault(foo, Bar(foo))

# We can either have
bar = get_foobar(Foo())
# Bar must hold a strong reference to foo

# Or
foo = Foo()
bar = get_foobar(foo)
bar2 = get_foobar(foo)  # Same Bar
del bar
del bar2
bar3 = get_foobar(foo)  # Same Bar
# In this case, as long as foo exists, we want the same bar to show up,
# therefore, foo must in some way hold a strong reference back to bar

Теперь вот сложная часть: вы можете решить эту проблему, используя циклическую ссылку, где foo ссылки bar и bar ссылкиfoo, но эй, а что тут интересного?Очистка займет больше времени, не будет работать, если Foo определит __slots__ и, как правило, будет плохим решением.

Есть ли способ, я могу создать отображение foo_to_bar, которое очищает одиночный ссылка на foo и bar?По сути:

import weakref
foo_to_bar = weakref.WeakKeyDictionary()
# If bar is referenced only once (as the dict value) and foo is
# referenced only once (from bar.inst) their mapping will be cleared out

Таким образом, он может отлично работать, поскольку наличие foo вне функции гарантирует, что bar все еще там (мне может потребоваться __slots__ на Foo для поддержки __weakref__) и наличие bar вне функции приводит к тому, что foo все еще остается там (из-за сильного задания в Bar).

WeakKeyDictionary не работает, поскольку {weakref.ref(inst): bar.inst} приведет к циклическому обращению.

В качестве альтернативы, есть ли способ подключиться к механизму подсчета ссылок (для очистки, когда оба объекта получают по 1 ссылке каждый) без значительных накладных расходов?

1 Ответ

0 голосов
/ 21 июня 2018

Вы думаете об этом.Вам не нужно отслеживать, если осталась только одна ссылка.Ваша ошибка состоит в том, чтобы сначала создать циклическую ссылку.

Хранить _BarInner объекты в вашем кэше, у которых нет ссылок на Foo экземпляров .Получив доступ к сопоставлению, верните легкий экземпляр Bar, который содержит ссылки _BarInner и Foo:

from weakref import WeakKeyDictionary
from collections.abc import Mapping


class Foo:
    pass


class Bar:
    """I must hold the instance in order to do stuff"""
    def __init__(self, inst, inner):
        self._inst = inst
        self._inner = inner

    # Access to interesting stuff is proxied on to the inner object,
    # with the instance information included *as needed*.
    @property
    def spam(self):
        self.inner.spam(self.inst)


class _BarInner:
    """The actual data you want to cache"""
    def spam(self, instance):
        # do something with instance, but *do not store any references to that
        # object on self*.


class BarMapping(Mapping):
    def __init__(self):
        self._mapping = WeakKeyDictionary()

    def __getitem__(self, inst):
        inner = self._mapping.get(inst)
        if inner is None:
            inner = self._mapping[inst] = _BarInner()
        return Bar(inst, inner)

Перевод этого значения в связанный проект bdict в комментариях вы можете существенно упростить ситуацию:

  • Не беспокойтесь об отсутствии поддержки слабых ссылок в проектах.Документируйте, что ваш проект будет поддерживать данные для каждого экземпляра только для типов, имеющих атрибут __weakref__.Этого достаточно.
  • Не делайте различий между типами слотов и без слотов.Всегда храните данные для каждого экземпляра отдельно от экземпляров.Это позволяет упростить ваш код.
  • То же самое касается флагов 'strong' и 'autocache'.Мухи должны всегда держать сильную ссылку.Данные для каждого экземпляра всегда должны храниться.
  • Используйте один класс для возвращаемого значения дескриптора.Тип ClassBoundDict - это все, что вам нужно.Сохраните в этом объекте данные instance и owner, переданные в __get__, и соответственно измените поведение в __setitem__.
  • Посмотрите на collections.ChainMap() для инкапсуляции доступа ксопоставления классов и экземпляров для доступа на чтение.
...