[译]在Python中安全使用析构函数

栏目: Python · 发布时间: 5年前

内容简介:作者:

作者: Eli Bendersky

本文适用于 Python 2.5 2.6—— 如果你看到 Python 3 有任何不同,请让我知道。

C++ 中,析构函数是一个非常重要的概念,它们是 RAII resource acquisition is initialization )的一个基本成分——在抛出异常的程序中,基本上是编写涉及资源分配与释放代码仅有的安全方式。

Python 中,析构函数的需求少得多,因为 Python 有进行内存管理的垃圾收集器。不过,虽然内存是最常见的分配资源,它不是唯一的。还有要关闭的套接字与数据库连接,要刷新的文件、缓冲与缓存,以及在一个对象用完时需要释放的另外几种资源。

因此 Python 有析构函数的概念—— __del__ 方法。出于某个原因, Python 社区里的许多人认为 __del__ 是邪恶的,不应该使用。不过,简单 grep 标准库显示,在我们使用且喜欢的类中使用了数以十计的 __del__ ,那么要点在哪里?在本文中,我将尝试澄清它(首先是为我自己),何时应该使用 __del__ ,以及如何使用。

简单的例子代码

首先一个基本例子:

class FooType ( object ):

def __init__ ( self , id ):

self .id = id

print self .id, 'born'

def __del__ ( self ):

print self .id, 'died'

ft = FooType( 1 )

这打印出:

1 born

1 died

现在,回忆由于一个引用计数垃圾收集器的使用, Python 在一个对象退出作用域时,不会清理它。在该对象的最后一个引用退出作用域时,才将清理它。下面是一个展示:

class FooType ( object ):

def __init__ ( self , id ):

self .id = id

print self .id, 'born'

def __del__ ( self ):

print self .id, 'died'

def make_foo ():

print 'Making...'

ft = FooType( 1 )

print 'Returning...'

return ft

print 'Calling...'

ft = make_foo()

print 'End...'

这打印出:

Calling...

Making...

1 born

Returning...

End...

1 died

在程序终止时调用了这个析构函数,不是在 ft 退出 make_foo 里的作用域时。

析构函数的替代品

在我继续之前,一个合适的揭露:对资源的管理, Python 提供了比析构函数更好的方法——上下文( context )。我不会把这变成上下文的一个教程,但你应该熟悉 with 语句,以及可以在内部使用的对象。例如,处理文件写入的最好方法是:

with open ( 'out.txt' , 'w' ) as of:

of.write( '222' )

这确保在退出 with 内部的代码块时,该文件被正确关闭,即使抛出异常。注意这展示了一个标准的上下文管理器。另一个是 threading.lock ,它返回一个非常适合在一个 with 语句中使用的上下文管理器。更多细节,阅读 PEP 343

虽然推荐, with 不总是适用的。例如,假设你有一个封装了某种数据库的对象,在该对象生命期结束时,必须提交并关闭该数据库。现在,假定该对象应该是某种大且复杂的类(比如一个 GUI 会话,或者一个 MVC 模型类)的一个成员变量。父亲在别的方法中不时地与该 DB 对象交互,因此使用 with 是不现实的。所需要的是一个起作用的析构函数。

析构函数何处走偏

为了解决我在上一段展示的用例,你可以采用 __del__ 析构函数。不过,知道这不总是工作良好是重要的。引用计数垃圾收集器的死对头是循环引用。下面是一个例子:

class FooType ( object ):

def __init__ ( self , id , parent):

self .id = id

self .parent = parent

print 'Foo' , self .id, 'born'

def __del__ ( self ):

print 'Foo' , self .id, 'died'

class BarType ( object ):

def __init__ ( self , id ):

self .id = id

self .foo = FooType( id , self )

print 'Bar' , self .id, 'born'

def __del__ ( self ):

print 'Bar' , self .id, 'died'

b = BarType( 12 )

输出:

Foo 12 born

Bar 12 born

噢……发生了什么?析构函数在哪里?下面是 Python 文档在这件事上的陈述:

在启用了可选的循环检测器(默认打开)时,检测垃圾的循环引用,但仅在不涉及 Python 层面的 __del__() 方法时,才能被清理。

Python 不知道销毁彼此持有循环引用的对象的安全次序,因此作为一个设计决策,它只是不对这样的方法调用析构函数!

那么,现在怎么办?

因为其缺陷,我们不应该使用析构函数吗?我非常吃惊地看到许多 Python 支持者认为这样,并建议使用显式的 close 方法。但我不同意——显式的 close 方法不那么安全,因为它们容易忘记调用。另外,在发生异常时(在 Python 里,它们随时出现),管理显式关闭变得非常困难且烦人。

我确实认为析构函数可以且应该在 Python 里被安全地使用。带着几分小心,这绝对可能。

首先以及最重要的,注意到合理的循环引用是罕见的。我故意说合理的( justified )——出现循环引用的大量使用是坏的设计以及有漏洞抽象的样本。

作为一个经验规则,资源尽可能由最底层的对象持有。不要在你的 GUI 会话里直接持有一个 DB 资源。使用一个对象封装这个 DB 连接,并在析构函数里安全地关闭它。 DB 对象没有理由持有你代码里其他对象的引用。如果这样——它违反了几个好的设计实践。

有时,在复杂代码中,依赖性注入( dependency injection )有助于防止循环引用,不过即使在你发现需要一个真循环引用的罕见情形里,也存在解决方案。 Python 为此提供了 weakref 模块。文档很快揭示,这正是我们这里所需要的:

一个对象的弱引用不足以保持对象存活:当一个被引用对象仅有的引用是弱引用时,垃圾收集可以自由地销毁这个被引用对象,并为其他对象重用其内存。弱引用的主要使用是实现缓存或持有大对象的映射,其中期望大对象不仅仅因为出现在缓存或映射中,而被保持存活。

下面是用 weakref 重写的前面的例子:

import weakref

class FooType ( object ):

def __init__ ( self , id , parent):

self .id = id

self .parent = weakref.ref(parent)

print 'Foo' , self .id, 'born'

def __del__ ( self ):

print 'Foo' , self .id, 'died'

class BarType ( object ):

def __init__ ( self , id ):

self .id = id

self .foo = FooType( id , self )

print 'Bar' , self .id, 'born'

def __del__ ( self ):

print 'Bar' , self .id, 'died'

b = BarType( 12 )

现在我们得到希望的结果:

Foo 12 born

Bar 12 born

Bar 12 died

Foo 12 died

这个例子里的小改动是,在 FooType 构造函数里,我使用 weakref.ref parent 引用赋值。这是一个弱引用,因此它不会真正创建一个环。因此 GC 看不到环,它销毁了这两个对象。

结论

Python 有经由 __del__ 方法的完美、可用的对象析构函数。对绝大多数用例,它工作良好,但堵塞在循环引用处。不过,循环引用通常是坏设计的一个迹象,它们很少是合理的。对极少数使用了合理的循环引用的用例里,使用弱引用很容易打破循环, Python weakref 模块里提供弱引用。

参考文献

在准备本文时,某些有用的链接:


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Code Reading

Code Reading

Diomidis Spinellis / Addison-Wesley Professional / 2003-06-06 / USD 64.99

This book is a unique and essential reference that focuses upon the reading and comprehension of existing software code. While code reading is an important task faced by the vast majority of students,......一起来看看 《Code Reading》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具