我不理解这个python __del__行为

48 投票
8 回答
61833 浏览
提问于 2025-04-16 18:13

有人能解释一下为什么下面的代码会这样运行吗:

import types

class Dummy():
    def __init__(self, name):
        self.name = name
    def __del__(self):
        print "delete",self.name

d1 = Dummy("d1")
del d1
d1 = None
print "after d1"

d2 = Dummy("d2")
def func(self):
    print "func called"
d2.func = types.MethodType(func, d2)
d2.func()
del d2
d2 = None
print "after d2"

d3 = Dummy("d3")
def func(self):
    print "func called"
d3.func = types.MethodType(func, d3)
d3.func()
d3.func = None
del d3
d3 = None
print "after d3"

输出结果是这样的(注意,d2的析构函数从来没有被调用),这是在python 2.7下的结果:

delete d1
after d1
func called
after d2
func called
delete d3
after d3

有没有办法“修复”这段代码,让析构函数被调用,但又不删除添加的方法?我的意思是,最好的地方放d2.func = None应该是在析构函数里面!

谢谢

[编辑] 根据前面几个回答,我想澄清一下,我并不是在询问使用__del__的优缺点。我试图创建一个最简短的函数,来展示我认为不太直观的行为。我假设创建了一个循环引用,但我不太确定为什么。如果可以的话,我想知道怎么避免循环引用……

8 个回答

11

你可以用 with 这个操作符来代替 __del__

https://web.archive.org/web/20201111211102/http://effbot.org/zone/python-with-statement.htm

就像处理文件对象一样,你可以这样做:

with Dummy('d1') as d:
    #stuff
# d's __exit__ method is guaranteed to have been called
48

你不能指望 __del__ 一定会被调用——这不是一个可以依赖的地方来自动释放资源。如果你想确保某个(非内存)资源被释放,你应该创建一个 release() 或类似的方法,然后明确地调用它(或者像Thanatos在下面的评论中提到的那样,使用一个上下文管理器)。

至少你应该仔细阅读 __del__文档,然后你可能应该避免使用 __del__。(另外,关于 __del__ 的其他不好的事情,请参考 gc.garbage文档

29

我自己来回答这个问题,因为虽然我明白大家建议不要使用 __del__,但我想知道的是如何让它在我提供的代码示例中正常工作。

简短版本:以下代码使用 weakref 来避免循环引用。我原以为在发问之前已经尝试过这个方法,但看来我可能做错了什么。

import types, weakref

class Dummy():
    def __init__(self, name):
        self.name = name
    def __del__(self):
        print "delete",self.name

d2 = Dummy("d2")
def func(self):
    print "func called"
d2.func = types.MethodType(func, weakref.ref(d2)) #This works
#d2.func = func.__get__(weakref.ref(d2), Dummy) #This works too
d2.func()
del d2
d2 = None
print "after d2"

详细版本:当我发问的时候,我确实搜索过类似的问题。我知道可以用 with 来替代,而且大家普遍认为 __del__不好的

使用 with 是有道理的,但只在某些情况下适用。比如打开一个文件、读取它,然后关闭,这就是一个很好的例子,使用 with 完全没问题。在这个特定的代码块中,你需要这个对象,并且希望在代码块结束时清理掉它。

数据库连接常常被用作不适合使用 with 的例子,因为你通常需要在创建连接的代码块之外关闭连接,而这通常是基于事件驱动的(而不是顺序执行的)。

如果 with 不是合适的解决方案,我看到两个替代方案:

  1. 确保 __del__ 能正常工作(可以参考 这篇博客,它对 weakref 的使用有更好的描述)
  2. 使用 atexit 模块,在你的程序关闭时运行一个回调。可以参考 这个话题

虽然我试图提供简化的代码,但我真正的问题更像是事件驱动的,所以 with 并不是合适的解决方案(对于简化的代码来说 with 是可以的)。我还想避免使用 atexit,因为我的程序可能会长时间运行,我希望尽快进行清理。

所以,在这个特定情况下,我认为使用 weakref 来防止循环引用是最好的解决方案,这样可以让 __del__ 正常工作。

这可能是个例外,但在某些情况下,使用 weakref__del__ 是正确的实现,个人认为。

撰写回答