是否依赖于__del __()来清除python中的不可靠?

时间:2016-02-18 17:45:19

标签: python destructor

我正在阅读有关在Python中清理对象的不同方法,我偶然发现了这些问题(12),这些问题基本上说使用__del__()进行清理是不可靠的并且应避免使用以下代码:

def __init__(self):
    rc.open()

def __del__(self):
    rc.close()

问题是,我正在使用这段代码,而且我无法重现上述问题中引用的任何问题。据我所知,我不能用with语句来替代,因为我为闭源软件提供了一个Python模块(testIDEA,任何人?)这个软件将创建特定的实例类和处理它们,这些实例必须准备好在它们之间提供服务。我看到__del__()的唯一替代方法是根据需要手动调用open()close(),我认为这很容易出错。

我明白,当我关闭翻译时,我无法保证我的对象会被正确销毁(并且它不会让我感到烦恼,甚至是Python作者我觉得没关系。除此之外,我是否通过使用__del__()进行清理来玩火?

PS:我最初想在meta上发布一个关于可以复制的错误问题的咆哮,但意识到我不会得到我需要的技术细节。

3 个答案:

答案 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__是安全的。