Циркулярная ссылка - перерыв на одну ссылку

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


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

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

Предположим, у нас есть экземпляр некоторого класса 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 ответ

Решение

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

хранить _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() инкапсулировать доступ к классу и сопоставлениям экземпляров для доступа на чтение.
Другие вопросы по тегам