Python委托模式 - 如何避免循环引用?

7
我想请问在Python中使用委托模式是否会导致循环引用,如果是,那么如何最好地实现它以确保对象及其委托将被垃圾回收?
在Objective C中,通过使用弱引用来避免上述问题。在C++中,我们不会调用代理的delete方法。我在这里找到了Python的weakref模块链接:http://docs.python.org/library/weakref.html。似乎一个合理的方法可能是使用该模块创建一个弱引用来引用实例变量,但我不确定。
由于我在Google上搜索这个问题没有找到答案,我想知道在Python中是否存在这样的问题,或者是否有普遍的解决方案(不需要weakref模块)我不知道的?此外,提问之前我已经在stackoverflow上进行了搜索,但我发现的问题要么涉及循环导入,要么涉及常规委托模式,而不涉及特定于Python和循环引用的问题。
非常感谢您提供的任何回复。
下面列出了一些玩具示例代码,以帮助说明我的问题。我已按此方式实现了代码,并且它可以工作,但我不确定内存是否在最后被垃圾回收。
class A(object):
    def __init__(self):
        self.delegate = None

        # Some other instance variables that keep track of state for performing some tasks.

    def doSomething(self):
        if self.delegate is not None:
            self.delegate.doSomething()
        else:
            print('Cannot perform task because delegate is not set.')

    # Other methods not shown.

class B(object):
    def __init__(self):
        self.a = A() # Need to keep object 'a' from garbage collected so as to preserve its state information.
        self.a.delegate = self  # Is this a circular reference? How to 'fix' it so that A and B will eventually be garbage collected?

    def doSomething(self):
        print('B doing something')

    # Other methods not shown.

编辑:

阅读了一些回复后,我决定澄清我的问题。我知道Python有垃圾收集。我不确定的是它是否会对循环引用的对象执行垃圾收集。我的担忧来自Python文档中以下段落:

CPython实现细节: CPython当前使用引用计数方案和(可选的)延迟检测循环链接的垃圾收集,它会在大多数对象变得无法访问时立即收集,但不能保证收集包含循环引用的垃圾。请参见gc模块的文档以获取有关控制循环垃圾收集的信息。其他实现的行为不同,并且CPython可能会更改。在对象变得无法访问时不要依赖于立即完成(例如:始终关闭文件)。

可以在这里找到原始形式的文本: http://docs.python.org/reference/datamodel.html 粗体为我的设置。

下面的帖子提供了有关循环引用对象问题的更清晰的解释,以及为什么该问题将防止对这些对象进行垃圾收集(至少在典型设置中): http://www.electricmonk.nl/log/2008/07/07/python-destructor-and-garbage-collection-notes/

此外,我刚刚看到亚历克斯·马特利在以下问题的回复中解释了Python用户是否应该担心循环引用: Should I worry about circular references in Python? 从他的答案中,我得出结论,即使循环引用对象最终会被垃圾收集但会有开销。它是否显著取决于程序。

此外,他提到使用Python的weakref模块,但没有明确说明如何使用。

因此,我想添加以下问题以澄清一些未解决的问题:

  1. 文档说对循环引用的对象不能保证进行垃圾收集。但是从回复中看来,似乎不是这种情况。那么我是否误解了这段文字,还是有进一步的细节我错过了?
  2. 我认为,如Alex的回复和我的问题所述,使用弱引用将完全避免这个问题的开销?

再次感谢回复。


1
呃,没有封装在属性中的getter/setter不是真正的Pythonic风格。 - ThiefMaster
@ThiefMaster:根据您的要求,我已经删除了有问题的setter :)。我最近才开始使用Python,所以如果我的问题显而易见,请原谅我。 - lightalchemist
2个回答

4

Python已经自带垃圾回收功能。只有在使用C编写自己的容器类型(作为扩展程序)时,才需要进行特殊处理。

示例:运行此程序并观察内存使用情况,它不会上升。

class C(object):
    pass

def circular():
    for x in range(10**4):
        for y in range(10**4):
            a = C()
            b = C()
            a.x = b
            b.x = a

circular()

注: 下面的函数没有任何作用,请将其删除。

def setDelegate(self, delegate):
    self.delegate = delegate

不需要调用x.setDelegate(y),可以使用x.delegate = y。在Python中,您可以重载成员访问,因此编写方法没有任何优点。


嗨,我编辑了我的问题,以解释为什么我认为处理循环引用对象时会出现垃圾回收问题。您和Alex Martelli的答案(在我的编辑问题中引用)似乎已经回答了我的问题,即这些对象最终是否会被垃圾回收。因此,我将接受您的答案。但是,如果有人能指出我在编辑问题中提出的问题,那就太好了。 - lightalchemist
如果您想得到答案,请将其作为单独的问题提出。 - Dietrich Epp
嗨,我已经在这里重新发布了我的扩展问题[链接](https://dev59.com/vmPVa4cB1Zd3GeqP-e4c)。谢谢。 - lightalchemist

0

为什么它在结束时不会被垃圾回收?当脚本结束并且Python完成执行时,整个内存部分将被标记为垃圾回收和(最终)操作系统恢复。

如果您在长时间运行的程序中运行此代码,在A和B都取消引用后,内存将被回收。


您好,我扩展了我的问题以澄清我认为垃圾收集存在问题的原因。无论如何,感谢您的回复。 - lightalchemist
你可能应该提出一个新问题。 - Jordan
嗨。谢谢你的提示。我已经在这里重新发布了我的问题。 - lightalchemist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接