我正在阅读有关在Python中清理对象的不同方法,我偶然发现了这些问题(1,2),这些问题基本上说使用__del__()
进行清理是不可靠的并且应避免使用以下代码:
def __init__(self):
rc.open()
def __del__(self):
rc.close()
问题是,我正在使用这段代码,而且我无法重现上述问题中引用的任何问题。据我所知,我不能用with
语句来替代,因为我为闭源软件提供了一个Python模块(testIDEA,任何人?)这个软件将创建特定的实例类和处理它们,这些实例必须准备好在它们之间提供服务。我看到__del__()
的唯一替代方法是根据需要手动调用open()
和close()
,我认为这很容易出错。
我明白,当我关闭翻译时,我无法保证我的对象会被正确销毁(并且它不会让我感到烦恼,甚至是Python作者我觉得没关系。除此之外,我是否通过使用__del__()
进行清理来玩火?
答案 0 :(得分:3)
您在垃圾收集语言中观察到终结器的典型问题。 Java拥有它,C#拥有它,并且它们都提供了基于范围的清理方法,如Python with
关键字来处理它。
主要问题是,垃圾收集器负责清理和销毁对象。在C ++中,对象在超出范围时会被销毁,因此您可以使用RAII并具有明确定义的语义。在Python中,只要GC喜欢,对象就会超出范围并继续存在。根据您的Python实现,这可能会有所不同。 CPython及其基于refcounting的GC非常温和(所以你很少看到问题),而PyPy,IronPython和Jython可能会让对象保持很长时间。
例如:
def bad_code(filename):
return open(filename, 'r').read()
for i in xrange(10000):
bad_code('some_file.txt')
bad_code
泄漏文件句柄。在CPython中它并不重要。引用计数降为零,并立即删除。在PyPy或IronPython中,您可能会遇到IOErrors或类似问题,因为您耗尽了所有可用的文件描述符(在Unix上最多ulimit
或在Windows上最多509个句柄。)
如果您需要保证清理,最好使用上下文管理器和with
进行基于范围的清理。您确切知道何时最终确定对象。但有时你不能轻易地强制执行这种范围的清理。多数民众赞成你可以使用__del__
,atexit
或类似的结构来尽最大努力清理。它不可靠,但总比没有好。
您可以通过显式清理或强制显式范围来为您的用户增加负担,或者您可以使用__del__
进行赌博并偶尔看到一些奇怪的事情(尤其是解释器关闭)。
答案 1 :(得分:2)
使用__del__
运行代码存在一些问题。
首先,它只有在你积极跟踪引用时才有效,即便如此,除非你在整个代码中手动启动垃圾收集,否则不能保证它会立即运行。我不了解你,但是在准确跟踪引用方面,自动垃圾收集几乎让我失望。即使您在代码中非常勤奋,您也依赖其他用户,这些用户在引用计数方面同样勤奋。
二,有很多实例永远不会运行__del__
。在初始化和创建对象时是否存在异常?口译员退出了吗?某处有循环引用吗?是的,很多可能在这里出错,很少有办法干净利落地处理它。
三,即使它确实运行,它也不会引发异常,所以你不能像使用其他代码那样处理异常。几乎不可能保证来自各种对象的__del__
方法将以任何特定顺序运行。所以析构函数最常见的用例 - 清理和删除一堆对象 - 是没有意义的,不太可能按计划进行。
如果你真的想要运行代码,那么有更好的机制 - 上下文管理器,信号/插槽,事件等。
答案 2 :(得分:1)
如果您使用的是CPython,则一旦对象的引用计数为零,__del__
就会完全可靠且可预测地触发。 https://docs.python.org/3/c-api/intro.html处的文档状态:
当对象的引用计数变为零时,该对象将被释放。如果它包含对其他对象的引用,则其引用计数将减少。如果此递减使它们的引用计数变为零,则可以依次释放这些其他对象,等等。
您可以轻松地进行测试并查看自己立即进行的清理:
>>> class Foo:
... def __del__(self):
... print('Bye bye!')
...
>>> x = Foo()
>>> x = None
Bye bye!
>>> for i in range(5):
... print(Foo())
...
<__main__.Foo object at 0x7f037e6a0550>
Bye bye!
<__main__.Foo object at 0x7f037e6a0550>
Bye bye!
<__main__.Foo object at 0x7f037e6a0550>
Bye bye!
<__main__.Foo object at 0x7f037e6a0550>
Bye bye!
<__main__.Foo object at 0x7f037e6a0550>
Bye bye!
>>>
(尽管如果您想在REPL上测试涉及__del__
的内容,请注意,最后计算出的表达式的结果将存储为_
,这将作为参考。)
换句话说,如果您的代码严格将在CPython中运行,那么依靠__del__
是安全的。