Python:删除自引用对象

4
我希望向你询问如何在Python中删除具有自引用的对象。让我们考虑一个类,这是一个简单的例子,了解它何时被创建和何时被删除:
#!/usr/bin/python
class TTest:
  def __init__(self):
    self.sub_func= None
    print 'Created',self
  def __del__(self):
    self.sub_func= None
    print 'Deleted',self
  def Print(self):
    print 'Print',self

这个类有一个变量self.sub_func,我们假设要给它分配一个函数。我想使用TTest的实例来给self.sub_func分配一个函数。请看下面的示例:

def SubFunc1(t):
  t.Print()
def DefineObj1():
  t= TTest()
  t.sub_func= lambda: SubFunc1(t)
  return t

t= DefineObj1()
t.sub_func()
del t

结果是:
Created <__main__.TTest instance at 0x7ffbabceee60>
Print <__main__.TTest instance at 0x7ffbabceee60>

也就是说,虽然我们执行了"del t",但是t并没有被删除。我猜测原因是t.sub_func是一个自引用对象,因此在"del t"时,t的引用计数器没有变为零,因此垃圾回收器没有删除t。
为了解决这个问题,我需要插入:
t.sub_func= None

在执行 "del t" 命令之前,输出结果为:
Created <__main__.TTest instance at 0x7fab9ece2e60>
Print <__main__.TTest instance at 0x7fab9ece2e60>
Deleted <__main__.TTest instance at 0x7fab9ece2e60>

但这很奇怪。 t.sub_func是t的一部分,因此我不想在删除t时清除t.sub_func。
你能告诉我是否知道一个好的解决方案吗?

3
Python 垃圾回收器可以检测出循环引用。在多年的 Python 编程经验中,我从未使用过弱引用,而且 非常少 需要显式删除。你确定需要吗?你有内存问题吗? - salezica
或许在 del 后调用 gc.collect() - Reut Sharabani
谢谢大家。我试过gc.collect()但没有用。我的Python版本是2.7.5+。实际上,我一直依赖Python的GC,但我意识到,由于这个问题,一些我想释放的对象没有被释放。其中一些使用了大量内存。我期望这样的对象在函数结束时被释放,但它仍然存在,所以我需要重写每一段代码,这很奇怪。 - Akihiko
我还没有考虑过弱引用。 - Akihiko
最好的方法是设计您的软件,完全不使用循环引用。如果必须使用,则应使用弱引用。 - Keith
显示剩余2条评论
2个回答

4
如何确保一个引用循环中的对象在不可达时被删除?最简单的解决方案是不定义__del__方法。很少有类需要__del__方法。Python并不保证何时甚至是否调用__del__方法。
有几种方法可以缓解这个问题。
1.使用函数而不是包含和检查弱引用的lambda表达式。每次调用函数时,需要显式检查对象是否仍然存在。
2.为每个对象创建一个唯一的类,以便我们可以将函数存储在类上而不是作为猴子补丁函数。这可能会得到较大的内存开销。
3.定义一个属性,该属性知道如何获取给定的函数并将其转换为方法。我个人最喜欢的方式,因为它接近于如何从类的未绑定方法创建绑定方法。

使用弱引用

import weakref

class TTest:
    def __init__(self):
        self.func = None
        print 'Created', self
    def __del__(self):
        print 'Deleted', self
    def print_self(self):
        print 'Print',self

def print_func(t):
    t.print_self()

def create_ttest():
    t = TTest()
    weak_t = weakref.ref(t)
    def func():
        t1 = weak_t()
        if t1 is None:
            raise TypeError("TTest object no longer exists")
        print_func(t1)
    t.func = func
    return t

if __name__ == "__main__":
    t = create_ttest()
    t.func()
    del t

创建一个独特的类

class TTest:
    def __init__(self):
        print 'Created', self
    def __del__(self):
        print 'Deleted', self
    def print_self(self):
        print 'Print',self

def print_func(t):
    t.print_self()

def create_ttest():
    class SubTTest(TTest):
        def func(self):
            print_func(self)
    SubTTest.func1 = print_func 
    # The above also works. First argument is instantiated as the object the 
    # function was called on.
    return SubTTest()

if __name__ == "__main__":
    t = create_ttest()
    t.func()
    t.func1()
    del t

使用属性

import types

class TTest:
    def __init__(self, func):
        self._func = func
        print 'Created', self
    def __del__(self):
        print 'Deleted', self
    def print_self(self):
        print 'Print',self
    @property
    def func(self):
        return types.MethodType(self._func, self)

def print_func(t):
    t.print_self()

def create_ttest():
    def func(self):
        print_func(self)
    t = TTest(func)
    return t

if __name__ == "__main__":
    t = create_ttest()
    t.func()
    del t

谢谢你提供的三个想法。我最喜欢第一个(使用弱引用),因为它代码量少,易于理解(它接近 C/C++ 的指针),而且直观。我认为,在我的情况下,“显式检查对象是否存在”是不必要的,因为“func”总是作为“t.func”执行,这意味着 t(及其弱引用)存在。 - Akihiko

3
CPython官方文档中得知:
具有__del__()方法并且属于引用循环的对象会导致整个引用循环无法收集,包括不一定在循环中但仅从循环中可达的对象。Python不会自动收集这些循环,因为通常情况下,Python无法猜测运行__del__()方法的安全顺序。如果您知道安全顺序,则可以通过检查垃圾列表并显式地断开列表中对象的循环来强制执行。请注意,这些对象即使在垃圾列表中也会保持活动状态,因此也应该将其从垃圾中删除。例如,在断开循环后,执行del gc.garbage[:]以清空列表。通常最好避免创建包含具有__del__()方法的对象的循环,并且在这种情况下可以检查垃圾以验证是否正在创建此类循环。
另请参阅:http://engineering.hearsaysocial.com/2013/06/16/circular-references-in-python/

从技术上讲,我的问题似乎是一种循环引用。但它的实际含义显然不同。在有问题的部分中存在层次结构:t.sub_func = lambda: SubFunc1(t); SubFunc1(t) 是 t 的子元素。问题在于 Python 无法识别这种层次结构。您认为使用 weakref 是告诉 Python 此信息的适当方式吗? - Akihiko
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Blckknght

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