两个对象之间的循环引用是否需要使用weakref?

Does circular reference between two objects requires the use of weakref?

我正在尝试实现符合以下原则的东西:

from weakref import WeakValueDictionary

class Container(object):
    def __init__(self):
        self.dic = WeakValueDictionary({})
    def put_in(self, something):
        self.dic[something] = Thing(self, something)

class Thing(object):
    def __init__(self, container, name):
        self.container = container
        self.name = name

    def what_I_am(self):
        print("I am a thing called {}".format(self.name))

pot = Container()
pot.put_in('foo')
pot.dic['foo'].what_I_am()

但我得到:

  File "C:/Users/jacques/ownCloud/dev/weakref.py", line 26, in <module>
    pot.dic['foo'].what_I_am()
  File "C:\Program Files\Anaconda3\lib\weakref.py", line 131, in __getitem__
    o = self.data[key]()
KeyError: 'foo'

我知道我的实现不正确,因为 Thing 实例被 GC 处理并从 WeakValueDictionary 中删除。

有什么方法可以实现这样的目的来防止 ContainerThing 之间的循环引用?

编辑:如果我为下面的代码更改上面的代码,它会解决循环引用问题吗?

from weakref import proxy

class Container(dict):
    def put_in(self, something):
        self[something] = Thing(self)

class Thing(object):
    def __init__(self, container):
        self.container = proxy(container)

    def what_is_it(self):
        print("I am a thing called {}".format(self))

    def __getattr__(self, name):
        try: #Look up the Thing instance first
            return object.__getattribute__(self, name)
        except AttributeError: #Try to find the attribute in container
            return self.container.__getattribute__(name)

    def __format__(self, spec):
        (name,) = (key for key, val in self.container.items() if self == val)
        return name

pot = Container()
pot.location = 'Living room'
pot.put_in('foo')
pot['foo'].what_is_it()
print(pot['foo'].location)

WeakValueDictionary 的关键在于,一旦对象不再使用,它​​的键就会自动删除。

紧接着

self.dic[thing] = Thing(self)

不再有对 WeakValueDictionary 之外的 Thing 对象的引用,因此您看到的行为是正确的并且符合预期。

如果您希望密钥可以访问,请将 WeakValueDictionary 替换为常规 dict。或者,确保存在对事物的引用,例如通过返回它或在其他地方引用它。

您无需担心循环引用。 Python 在这种情况下完全有能力管理自己的内存。并将在必要时删除具有循环引用的对象。

您的实施只需如下所示:

class Container(dict):
    def put_in(self, something):
        self[something] = Thing(self, something)

class Thing:
    def __init__(self, container, name):
        self.container = container
        self.name = name

    def what_is_it(self):
        assert self.container[self.name] is self, "Thing stored under wrong name"
        print("I am a thing called {}".format(self.name))

    def __getattr__(self, name):
        # By the time __getattr__ is called, normal attribute access on Thing has
        # already failed. So, no need to check again. Go straight to checking the 
        # container
        try:
            return getattr(self.container, name)
        except AttributeError:
            # raise a fresh attribute error to make it clearer that the 
            # attribute was initially accessed on a Thing object
            raise AttributeError("'Thing' object has no attribute {!r}".format(name)) from e

向您展示其工作原理的快速测试:

c = Container()
c.put_in("test")
c.value = 0

# Attribute demonstration
c["test"].what_is_it()
t = c["test"]
print("name:", t.name) # get a Thing attribute
print("value:", t.value) # get a Container Attribute
c.name = "another name"
print("Thing name:" t.name) # gets Thing attrs in preference to Container attrs

# Garbage collection demonstration
import weakref
import gc

r = weakref.ref(c["test"])
del c, t
# no non-weak references to t exist anymore
print(r()) # but Thing object not deleted yet
# collecting circular references is non-trivial so Python does this infrequently

gc.collect() # force a collection
print(r()) # Thing object has now been deleted