代码之家  ›  专栏  ›  技术社区  ›  Bharel

循环参考-单个参考上的中断

  •  -1
  • Bharel  · 技术社区  · 6 年前

    TL;医生 是否有任何方法可以创建一个弱引用,在留下1个强引用而不是0时调用回调?


    对于那些认为这是x y问题的人来说,下面是一个很长的解释:

    我有一个非常具有挑战性的问题,我正试图用我的代码来解决。

    假设我们有一个类foo的实例,以及一个不同的类栏,它在使用实例时引用该实例:

    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 酒吧 参考文献 FOO公司 但是,嘿,其中有什么有趣的部分?如果foo定义的话,清理时间会更长,不会起作用。 __slots__ 一般来说,这是一个糟糕的解决方案。

    有没有办法,我可以创建一个 foo_to_bar 映射在 单一的 对两者的引用 FOO公司 酒吧 是吗?本质上:

    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公司 在功能之外确保 酒吧 还在吗(我可能需要 _插槽__ Foo 支持 __weakref__ )并且拥有 酒吧 在函数外部导致 FOO公司 仍然在那里(因为在 Bar )。

    WeakKeyDictionary 不工作,因为 {weakref.ref(inst): bar.inst} 将导致循环引用。

    或者,是否有任何方法可以钩住引用计数机制(以便在两个对象都达到1个引用时进行清理),而不会产生大量开销?

    1 回复  |  直到 6 年前
        1
  •  1
  •   Martijn Pieters    6 年前

    你想得太多了。如果只剩下一个引用,则不需要跟踪。您的错误是首先创建一个循环引用。

    商场 _BarInner 缓存中具有 没有引用 Foo 实例 .访问映射后,返回轻量级 Bar 同时包含 _巴里纳 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 owner 数据传递到 __get__ 在那个对象中,并且在 __setitem__ 因此。
    • 看看 collections.ChainMap() 封装对类和实例映射的访问以进行读取访问。