循环引用 - 单引号中断

时间:2018-06-11 23:30:43

标签: python python-3.x garbage-collection weak-references circular-reference

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引用barbar引用foo,但是嘿那个有趣的部分是什么?清理需要更长的时间,如果Foo定义__slots__并且通常是一个糟糕的解决方案,则无法工作。

有没有办法,我可以创建一个foo_to_bar映射,清除对foobar单个引用?实质上:

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个引用时进行清理)而不会产生大量开销?

1 个答案:

答案 0 :(得分:1)

您太想了。您无需跟踪是否仅剩一个参考。您的错误是首先创建一个循环引用。

在您的缓存中存储_BarInner个对象,这些对象没有对Foo实例的引用。访问该映射后,返回一个轻量级Bar实例,该实例同时包含_BarInnerFoo引用:

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'标志也是如此。举重运动员应始终保持有力的参考。每个实例的数据应始终存储。
  • 使用单个类作为描述符的返回值。 ClassBoundDict类型就是您所需要的。将传递给instance的{​​{1}}和owner数据存储在该对象中,并相应地更改__get__中的行为。
  • 查看collections.ChainMap()来封装对类和实例映射的访问以进行读取访问。