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