Python:删除自引用对象



我想问一下如何在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是一个自引用对象,所以 t 的引用计数器在"del 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

你能告诉我你是否知道一个好的解决方案吗?

如何确保引用周期中的对象在不再可访问时被删除?最简单的解决方案是不定义__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

来自官方 CPython 文档:

具有__del__()方法并且是参考循环一部分的对象会导致整个参考循环不可收集,包括不一定在循环中但只能从循环中访问的对象。Python 不会自动收集这样的循环,因为一般来说,Python 不可能猜测运行__del__()方法的安全顺序。如果您知道安全顺序,则可以通过检查垃圾列表并显式中断由于列表中的对象而导致的循环来强制解决问题。请注意,即使如此,这些对象也会由于在垃圾列表中而保持活动状态,因此也应该将它们从垃圾箱中删除。例如,在中断循环后,执行del gc.garbage[:]清空列表。通常,最好不要使用 __del__() 方法创建包含对象的循环来避免此问题,在这种情况下,可以检查垃圾以验证是否未创建此类循环。

另请参阅:http://engineering.hearsaysocial.com/2013/06/16/circular-references-in-python/

最新更新