我不明白这个python __del__行为

Bre*_*yer 33 python del

有人可以解释为什么以下代码的行为方式如下:

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"
Run Code Online (Sandbox Code Playgroud)

输出(注意d2的析构函数从不被调用)是这个(python 2.7)

delete d1
after d1
func called
after d2
func called
delete d3
after d3
Run Code Online (Sandbox Code Playgroud)

有没有办法"修复"代码,以便调用析构函数而不删除添加的方法?我的意思是,放置d2.func = None的最佳位置是析构函数!

谢谢

[编辑]根据前几个答案,我想澄清一点,我不是在询问使用的优点(或缺乏优点)__del__.我试图创建最短的函数,以证明我认为是非直观的行为.我假设已经创建了一个循环引用,但我不确定为什么.如果可能的话,我想知道如何避免循环引用....

Nic*_*tin 35

你不能假设__del__将永远被调用 - 它不是一个希望资源自动解除分配的地方.如果要确保释放(非内存)资源,则应该创建一个release()或类似的方法,然后显式调用它(或者在上面的注释中由Thanatos指出的上下文管理器中使用它).

至少你应该仔细阅读__del__ 文档,然后你可能不应该尝试使用__del__.(另请参阅有关其他不良内容的gc.garbage 文档__del__)

  • 另外,`with`语句可以与`release()`类似的函数一起使用来编写更简单,更清晰的代码. (11认同)
  • 如果"明确"总是一个好主意,为什么要进行垃圾收集呢?"记得称之为"释放功能是脆弱的.您可能忘记调用它 - 或者可能在意外点引发异常并且您可能永远不会达到释放功能.[本文](http://eli.thegreenplace.net/2009/06/12/safely-using-destructors-in-python/)解释了如何保证调用__del__函数.如果可能的话,上下文管理器甚至更好,但是大部分时间都不可能,因为对象的生命周期超越了一个方法或函数. (7认同)
  • @TomSwirly:你*可能*能够保证你的对象有资格进行垃圾收集,但你不能保证*什么时候会被收集.如果垃圾收集器可以自由地收集对象,它也可以自由地*不*收集它.来自Raymond Chen的[博客文章](http://blogs.msdn.com/b/oldnewthing/archive/2010/08/09/10047586.aspx)总值得记住.假设对于给定的GC实现过多,可能会采用今天的工作代码并明天神秘地破解它. (2认同)
  • @TomSwirly如果您无法确定在其中保存并释放特定资源的单个范围,则应重新考虑代码的组织方式。最好创建一个单一的合并范围,使您可以获取和释放资源,这是一个最佳计划,即使需要进行大量重构也是如此。然后,您可以将资源对象传递给函数调用或您所拥有的对象,并根据需要继续将其传递到堆栈中。通常,您可以找到一种只保留一小段代码的资源的方法(在打开要写入的文件之前生成内容,在读取数据后立即关闭连接)。 (2认同)

Bre*_*yer 16

我提供了自己的答案,因为虽然我很欣赏要避免的建议__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"
Run Code Online (Sandbox Code Playgroud)

更长版本:当我发布问题时,我确实搜索了类似的问题.我知道你可以使用with,而流行的情绪__del__不好的.

使用with有意义,但仅限于某些情况.打开一个文件,阅读它并关闭它是一个很好的例子,它with是一个非常好的解决方案.你已经去了需要对象的特定代码块,并且你想要清理对象和块的结尾.

数据库连接似乎经常被用作不能正常使用的示例with,因为您通常需要保留创建连接的代码部分并在更加事件驱动(而非顺序)的时间范围内关闭连接.

如果with不是正确的解决方案,我会看到两种选择:

  1. 你确保__del__工作(有关weakref用法的更好描述,请参阅此博客)
  2. atexit程序关闭时,您可以使用该模块运行回调.例如,请参阅此主题.

虽然我试图提供简化的代码,但我的真正问题更多是事件驱动的,因此with不是一个合适的解决方案(with对简化代码来说很好).我也想避免atexit,因为我的程序可以长时间运行,并且我希望能够尽快执行清理.

因此,在这种特定情况下,我发现它是使用weakref和防止循环引用的最佳解决方案,这将阻止__del__工作.

这可能是规则的一个例外,但是有一些使用情况,weakref并且__del__是正确的实现,恕我直言.

  • -1.您必须知道如何提出问题,或者至少知道如何回答问题.请参阅[此元问题](http://meta.stackexchange.com/q/179543/)和[关于XY问题的这一个](http://meta.stackexchange.com/q/66377/).你问"为什么`__del__`似乎没有用",这是一个关于python内部的合理问题,你得到了一个合理的答案.你真正的问题是"我如何在一个事件处理程序中关闭我在另一个事件中打开的资源",其答案与`__del__`无关. (7认同)
  • 记住,无论是调用`__del__`,调用它还是被调用多少次都无法保证是非常重要的.有各种各样的方法,由你控制,垃圾收集发生谁知道什么时候.特别是,cPython是*********python,它引用计数并急切地收集这些垃圾.Jython,PyPy(和IronPython,我认为)只在扫描期间收集垃圾.取决于`__del__`是最可靠的灾难之路. (6认同)
  • @Aryeh - 我不同意.我的问题是如何使python的功能工作,我确实提供了正确的答案.如果真正的答案是不使用__ del__,那么应该从python中删除__ del__.我相信upvotes让人们知道什么是普遍接受的,我的答案不是.没关系.但是,当它回答问题时,你怎么能说我的答案是错的(问题,而不是你错误地重述了它)? (6认同)
  • 关于数据库连接作为使用`with`效果不佳的示例,我*强烈*不同意.在程序的某个高范围内获取数据库通常非常简单,将其作为参数传递到代码的其他部分,然后在它们返回后释放它.它可能在传递参数和返回之间执行大量代码,但它仍然是一个非常明确定义的范围.支持使用全局数据库连接的那些代码,以解决这类问题. (5认同)

Fal*_*rri 11

您可以使用运算符代替delwith.

http://effbot.org/zone/python-with-statement.htm

就像文件类型对象一样,你可以这样

with Dummy('d1') as d:
    #stuff
#d is guaranteed to be out of scope
Run Code Online (Sandbox Code Playgroud)

  • `d`不在范围之外.唯一的保证是在`Dummy('d1')创建的对象上调用`__exit__`方法 (4认同)

Win*_*ert 8

del 不打电话 __del__

del在您使用的方式删除局部变量.__del__在对象被销毁时调用.Python作为一种语言并不能保证它何时会破坏一个对象.

CPython作为Python最常见的实现,使用引用计数.因此,del通常会按预期工作.但是,如果您有参考周期,它将无法工作.

d3 -> d3.func -> d3
Run Code Online (Sandbox Code Playgroud)

Python没有检测到这一点,所以不会马上清理它.而且它不仅仅是参考周期.如果抛出异常,您可能仍希望仍然调用析构函数.但是,Python通常会将局部变量作为其回溯的一部分.

解决方案不依赖于__del__方法.相反,使用上下文管理器.

class Dummy:
   def __enter__(self):
       return self

   def __exit__(self, type, value, traceback):
       print "Destroying", self

with Dummy() as dummy:
    # Do whatever you want with dummy in here
# __exit__ will be called before you get here
Run Code Online (Sandbox Code Playgroud)

这保证可以工作,您甚至可以检查参数以查看是否正在处理异常并在这种情况下执行不同的操作.