TL; DR 有没有办法创建一个弱引用,在有一个强引用而不是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
不起作用beacuse {weakref.ref(inst): bar.inst}
会导致循环引用。
或者,是否有任何方法可以挂钩引用计数机制(为了在两个对象都获得1个引用时进行清理)而不会产生大量开销?
答案 0 :(得分: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
project链接,可以大大简化操作:
__weakref__
属性的类型的按实例数据。够了。ClassBoundDict
类型就是您所需要的。将传递给instance
的{{1}}和owner
数据存储在该对象中,并相应地更改__get__
中的行为。collections.ChainMap()
来封装对类和实例映射的访问以进行读取访问。