来自Python文档:
不能保证在解释器退出时仍然存在的对象会调用
__del__()
方法。
据我所知,也没有办法保证一个对象在解释器退出前停止存在,因为由垃圾回收器决定对象删除的时间和方式。
那么,这个方法有什么意义呢?你可以在其中编写清理代码,但无法保证它会被执行。
虽然可以使用try
-finally
或with
子句来解决此问题,但我仍然想知道__del__()
方法的一个有意义的应用场景是什么。
来自Python文档:
不能保证在解释器退出时仍然存在的对象会调用
__del__()
方法。
据我所知,也没有办法保证一个对象在解释器退出前停止存在,因为由垃圾回收器决定对象删除的时间和方式。
那么,这个方法有什么意义呢?你可以在其中编写清理代码,但无法保证它会被执行。
虽然可以使用try
-finally
或with
子句来解决此问题,但我仍然想知道__del__()
方法的一个有意义的应用场景是什么。
它可以用于释放对象管理的资源:https://github.com/python/cpython/blob/master/Lib/zipfile.py#L1805
如文档字符串中所述,这是一种“最后的手段”,因为只有在gc运行时对象才会被关闭。
正如您在问题中提到的,首选方法是自己调用close
,可以直接调用.close()
或使用上下文管理器with Zipfile() as z:
您引用的来自{{link1:__del__
方法文档}}的段落如下:
不能保证在解释器退出时仍存在的对象会调用
__del__()
方法。
但是,不仅不能保证在解释器退出期间销毁的对象会调用__del__()
,甚至在正常执行期间也不能保证对象被垃圾回收,{{link2:来自Python语言参考手册中的“数据模型”部分}}:
对象从未被显式地销毁;然而,当它们变得不可达时,它们可能会被垃圾回收。允许实现推迟垃圾回收或完全省略垃圾回收 - 垃圾回收的实现质量是如何实现的问题,只要不收集仍然可达的对象。
因此,回答您的问题:
“那么,拥有这个方法的意义在哪里呢?你可以在其中编写清理代码,但不能保证它会被执行。”从一个与实现无关的角度来看,__del__
方法有什么用途,作为代码的基本组成部分可以依赖吗?“没有。完全没有。”从这个角度来看,它基本上是无用的。__del__
作为最后的手段机制来(尝试)确保在对象被销毁之前执行任何必要的清理,例如释放资源,如果用户忘记显式调用 close
方法。这不是一种救命稻草,而是一种“即使不能保证工作,增加额外的安全机制也不会有害”,实际上,大多数 Python 实现大多数时间都能捕获它。但不能依赖它。
话虽如此,如果您知道您的程序将在特定的Python实现集上运行,那么您可以依赖于垃圾回收的实现细节。例如,如果您使用CPython,则可以“依赖于”垃圾回收的事实,即在正常执行期间(即在解释器退出之外),如果非循环引用对象的引用计数达到零,它将被回收并调用其 __del__
方法,就像其他答案所指出的一样。从以上同一小节中:
CPython实现细节:CPython目前使用一个带有(可选)延迟检测的循环链接垃圾收集方案,该方案能够尽快地收集大多数成为不可访问的对象,但无法保证收集包含循环引用的垃圾。
但是,这仍然非常不稳定,而且不应该过度依赖,因为正如上面提到的,它仅对不属于循环引用图的对象进行了保证。
其他实现行为不同,而CPython也可能会发生变化。当对象变得不可达时,不要依赖于立即最终的处理(因此您应该始终明确关闭文件)。
从纯粹主义的角度来看,__del__
方法完全没有用处。从稍微不那么纯粹主义的角度来看,它仍然几乎没有用处。从实际角度来看,它可能作为你代码的一种补充特性有用,但绝不是必要的。
__del __()
方法。这样我们就可以像在C++中一样依赖它,而且就不需要(或者少需要)所有的with
子句了。看起来实现起来很简单,但也许我错过了一些使它变得复杂的实现方面。 - PieterNuyts它基本上被用于强制调用一个方法,该方法应在该对象的所有活动完成后调用一次,例如:
def __del__(self):
self.my_func()
现在,您可以确信当对象工作完成后将调用my_func
。
运行此程序,您就会知道发生了什么。
class Employee:
def __init__(self, name):
self.name = name
print('Employee created.')
def get_name(self):
return self.name
def close(self):
print("Object closed")
# destructor
def __del__(self):
self.close()
obj = Employee('John')
print(obj.get_name())
# lets try deleting the object!
obj.__del__() # you don't need to run this
print("Program ends")
print(obj.get_name())
输出
> Employee created.
> John
> Object closed
> Program ends
> John
> Object closed
John
会被再次打印?另外,我认为第二个 Object closed
的打印不是一定会发生的,因为标准规定:“当解释器退出时,不保证调用 del() 方法来处理仍然存在的对象。”我注意到这在某些 Python 实现中可以工作,但我认为您不能指望它。 - PieterNuyts您说:
不能保证在解释器退出时仍存在的对象都会调用 del() 方法。
这是很真实的,但有许多情况下,对象被创建后,对这些对象的引用要么被明确地设置为 None
而“销毁”,要么超出了范围。这些对象,在创建时或在执行过程中分配资源。当用户完成对象使用后,应该调用一个 close
或 cleanup
方法来释放那些资源。然而,最好还是有一个析构方法,即 __del__
方法,在没有更多引用指向对象时被调用,以检查是否已调用了清理方法,如果没有,则调用自身进行清理。在可能不会在退出时调用 __del__
的情况下,在这一点上重新获取资源可能并不太重要,因为程序正在被终止(当然,在 cleanup
或 close
执行更多的功能时,除了回收资源外,还要执行必要的终止函数,如关闭文件,那么依赖于在退出时调用 __del__
就会变得有问题)。
__del__
被调用:import sys
class A:
def __init__(self, x):
self.x = x
def __del__(self):
print(f'A x={self.x} being destructed.')
a1 = A(1)
a2 = A(2)
a1 = None
# a1 is now destroyed
input('A(1) should have been destroyed by now ...')
a_list = [a2]
a_list.append(A(3))
a_list = None # A(3) should now be destroyed
input('A(3) should have been destroyed by now ...')
a4 = A(4)
sys.exit(0) # a2 and a4 may or may not be garbage collected
输出:
A x=1 being destructed.
A(1) should have been destroyed by now ...
A x=3 being destructed.
A(3) should have been destroyed by now ...
A x=2 being destructed.
A x=4 being destructed.
除了a2
和a4
这两个对象,所有类A
的其他实例都将被“销毁”,即删除。
实际用法是,例如调用函数bar
创建一个B
实例,该实例又会创建一个A
实例。当函数bar
返回B
的引用和隐式销毁A
时,如果A
实例上的close
方法尚未被调用,则将自动进行清理:
class A:
def __init__(self, x):
self.x = x
self.cleanup_done = False
def close(self):
print(f'A x={self.x} being cleaned up.')
self.cleanup_done = True
def __del__(self):
if not self.cleanup_done:
self.close()
class B:
def __init__(self, x):
self.a = A(x)
def foo(self):
print("I am doing some work")
def bar():
b = B(9)
b.foo()
def other_function():
pass
if __name__ == '__main__':
bar()
other_function()
为了使B
实例显式调用A
实例的close
方法,它必须实现自己的close
方法,然后委托给A
实例的close
方法。但是,使用__del__
方法来达到这个目的没有意义。因为如果那样工作,那么A
实例自己的__del__
方法就足以进行清理。
__del__
在没有更多引用时被调用的回答是指针对 CPython 的。但是,编写 __del__
为类提供了额外的机会来进行清理,如果客户端忽略了调用 close
或 cleanup
或你选择称之为清理例程的任何其他方法。这是我的回答的主要观点,正如我们所说的,“迟做总比不做好”,即使在某个实现中垃圾回收被延迟了(如果完全省略垃圾回收,你也不会比以前更糟)。 - BoobooA(2)
,因为它仍然被a2
引用。你的演示确实说它被销毁了,但那可能只是在程序结束时(它也说a4
被销毁)。如果我是对的,下面的文本也是错误的。只是想看看你是否同意在编辑你的答案之前。 - PieterNuyts我最近发现一个需要实现__del__
的例子:
正如其他答案中提到的那样,在程序执行期间,无法保证__del__
会在何时甚至是否被调用。然而,如果你把这个语句反过来看,这也意味着它有可能在某个时刻被调用。
在我的情况下,我有一个依赖于初始化期间创建的临时文件的类,例如:
import tempfile
class A:
def __init__(self):
self.temp_file = tempfile.NamedTemporaryFile()
然而,我也依赖于一个外部程序,它可能会删除该文件(例如,如果其内容无效)。在这种情况下,如果临时文件与A的实例一起被回收,将会抛出异常,因为文件不再存在。
因此,解决方案是尝试在__del__
方法中关闭文件,并在那里捕获任何异常:
def __del__(self):
try:
self.temp_file.close()
except FileNotFoundError:
# Do something here
pass
__del__
,我可以指定如何处理资源,并在引发异常时采取什么措施。__del__()
,它将什么也不会做,如果文件不存在,也不会抛出任何异常,对吧?那么这种解决方案比不实现 __del__()
更好在哪里呢?当然,通过实现它,您增加了文件在某个时刻被正确关闭的机会,但这并不是保证。 - PieterNuytsFileNotFoundError
:https://pastebin.com/32F233w3。您需要使用`__del__`来捕获此异常并指定如何正确地摆脱此实例。尽管在这种情况下忽略了异常,因此您可能会认为这并不是什么大问题。 - Aratz析构函数在对象被销毁时调用。在Python中,由于Python具有自动处理内存管理的垃圾回收器,因此不像C++那样需要使用析构函数。
__del__()
方法在Python中被称为析构函数方法。当所有对该对象的引用都已删除即对象被垃圾回收时,将调用该方法。
析构函数声明的语法:
def __del__(self):
# body of destructor
注意:当对象失去引用或程序结束时,对象的引用也会被删除。
示例1:这是析构函数的简单示例。通过使用del关键字,我们删除了对象“obj”的所有引用,因此析构函数会自动调用。
# Python program to illustrate destructor
class Employee:
# Initializing
def __init__(self):
print('Employee created.')
# Deleting (Calling destructor)
def __del__(self):
print('Destructor called, Employee deleted.')
obj = Employee()
del obj
#Output
#Employee created.
#Destructor called, Employee deleted.
注意:析构函数在程序结束时或当所有对该对象的引用被删除时调用,即当引用计数变为零时,而不是在对象超出范围时。
示例2:此示例说明了上述注意事项。在这里,请注意,在打印“程序结束…”之后调用析构函数。
# Python program to illustrate destructor
class Employee:
# Initializing
def __init__(self):
print('Employee created')
# Calling destructor
def __del__(self):
print("Destructor called")
def Create_obj():
print('Making Object...')
obj = Employee()
print('function end...')
return obj
print('Calling Create_obj() function...')
obj = Create_obj()
print('Program End...')
#Output:
#Calling Create_obj() function...
#Making Object...
示例3:现在,考虑以下示例:
# Python program to illustrate destructor
class A:
def __init__(self, bb):
self.b = bb
class B:
def __init__(self):
self.a = A(self)
def __del__(self):
print("die")
def fun():
b = B()
fun()
#Output:
#die
shivam = person()
中,“shivam”是一个引用,指向内存中“person()”类的一个实例。 - Shivam Jha__del()__
的作用类似于析构函数,有时会通过垃圾回收器自动调用(我说有时而不是总是,因为你永远不知道它何时运行),所以使用它有点无用而不是有用。
__del__
在 Python 纯粹是基于引用计数的早期,更有意义。 - user2357112__del__
保证在实例即将被销毁时被调用。你引用的是解释器退出时销毁不被保证的情况。这是一个不同的故事,我认为在这种情况下 "try/finally" 或 "with" 并没有帮助。因此,__del__
可以很有用,例如当程序员犯错误并忘记使用 "try/finally" 或 "with" 时,可以自动回收资源。 - freakish