循环引用 - 中断单个引用
Circular reference - break on single reference
TL;DR 有没有办法创建一个弱引用,它会在剩下 1 个强引用而不是 0 个强引用时调用回调?
对于那些认为这是 X Y 问题的人,这里有详细的解释:
我有一个相当具有挑战性的问题,我正试图用我的代码解决。
假设我们有一个 class Foo 的实例,以及一个不同的 class 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
仍然存在(我可能需要 Foo
上的 __slots__
来支持 __weakref__
) 并且 bar
在函数外部导致 foo
仍然存在(因为 Bar
中的强引用)。
WeakKeyDictionary
不起作用,因为 {weakref.ref(inst): bar.inst}
会导致循环引用。
或者,有没有什么方法可以连接到引用计数机制(以便在两个对象都达到 1 个引用时进行清理)而不会产生重大开销?
你想多了。如果只剩下一个参考,则无需跟踪。你的错误是首先创建了一个循环引用。
在您的缓存中存储 _BarInner
个对象,这些对象没有引用 Foo
个实例。访问映射后,return 一个包含 _BarInner
和 Foo
引用的轻量级 Bar
实例:
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__
属性的类型的实例数据。够了。
- 不区分槽型和非槽型。始终将每个实例的数据存储在远离实例的地方。这可以让您简化代码。
- 'strong' 和 'autocache' 标志也是如此。享元应该始终保持强引用。应始终存储每个实例的数据。
- 对描述符 return 值使用单个 class。
ClassBoundDict
类型就是您所需要的。将传递给 __get__
的 instance
和 owner
数据存储在该对象中,并相应地改变 __setitem__
中的行为。
- 查看
collections.ChainMap()
以封装对 class 的访问和读取访问的实例映射。
TL;DR 有没有办法创建一个弱引用,它会在剩下 1 个强引用而不是 0 个强引用时调用回调?
对于那些认为这是 X Y 问题的人,这里有详细的解释:
我有一个相当具有挑战性的问题,我正试图用我的代码解决。
假设我们有一个 class Foo 的实例,以及一个不同的 class 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
仍然存在(我可能需要 Foo
上的 __slots__
来支持 __weakref__
) 并且 bar
在函数外部导致 foo
仍然存在(因为 Bar
中的强引用)。
WeakKeyDictionary
不起作用,因为 {weakref.ref(inst): bar.inst}
会导致循环引用。
或者,有没有什么方法可以连接到引用计数机制(以便在两个对象都达到 1 个引用时进行清理)而不会产生重大开销?
你想多了。如果只剩下一个参考,则无需跟踪。你的错误是首先创建了一个循环引用。
在您的缓存中存储 _BarInner
个对象,这些对象没有引用 Foo
个实例。访问映射后,return 一个包含 _BarInner
和 Foo
引用的轻量级 Bar
实例:
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__
属性的类型的实例数据。够了。 - 不区分槽型和非槽型。始终将每个实例的数据存储在远离实例的地方。这可以让您简化代码。
- 'strong' 和 'autocache' 标志也是如此。享元应该始终保持强引用。应始终存储每个实例的数据。
- 对描述符 return 值使用单个 class。
ClassBoundDict
类型就是您所需要的。将传递给__get__
的instance
和owner
数据存储在该对象中,并相应地改变__setitem__
中的行为。 - 查看
collections.ChainMap()
以封装对 class 的访问和读取访问的实例映射。