未加星标

Safely using destructors in Python

字体大小 | |
[开发(python) 所属分类 开发(python) | 发布者 店小二05 | 时间 2019 | 作者 红领巾 ] 0人收藏点击收藏

析构函数是 C++ 中一个非常重要的概念,析构函数(destructor) 与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。 析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。

而在 python 中没有专用的构造和析构函数,但是一般可以在 __init__ 和 __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里也同java一样采用了垃圾收集机制,不过不一样的是:

python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略

所以,当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

见输出最后一行,主体程序结束后才调用 FooType __del__ ,而不是当ft超出make_foo的作用域时就去调用 __del__ 。

Python 析构函数的替代品 上下文管理器

Python提供了一种更好的方法上下文来管理资源。上下文管理器允许你在有需要的时候,精确地分配和释放资源。

使用上下文管理器最广泛的案例就是with语句了。例如,处理写入文件时的最佳方法是:

with open('test.txt', 'w') as f: f.write('Voulez-vous coucher avec moi ce soir?')

这可以确保当系统中的块文件被正确关闭与退出,即使引发异常,它也会尝试去关闭文件,这就是 with 语句的主要优势。

不过,如果您的业务场景需要封装了某种类型的数据库,该对象必须在结束时提交并关闭。假设对象应该是某个大型复杂类的成员变量,父对象在不同的方法中需要不时得与 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

well, 发生了什么呢?以下是 Python 官方文档在此问题上的说法:

Circular references which are garbage are detected when the option cycle detector is enabled (it’s on by default), but can only be cleaned up if there are no Python-level del () methods involved.

文档中表明启用周期检测器时会检测到垃圾的循环引用(默认情况下它是打开的),但只有在没有涉及Python del () 方法的情况下才能清除。Python 不知道破坏彼此保持循环引用的对象的安全顺序,因此它则不会为这些方法调用析构函数。

解决方案

首先,我们可以使用 close() 方法来代替析构函数,但是这类方便并不是绝对安全的,不光是因为它们很容易在编程时忘记去正确调用,而且当程序抛出异常时,显式调用 close() 方法就会变得非常麻烦。

实际上,通过一些措施,析构函数是可以在Python中被安全使用的。

程序设计时尽可能减少不合理的循环引用;

资源应由最低级别的对象保存。不要在前台程序中直接保存DB资源。使用对象封装DB连接并在析构函数中安全地关闭它,DB对象没有任何理由在代码中保存对其他对象的引用。

依赖注入可以帮助防止复杂代码中的循环引用,但即使在极少数情况下,当您发现自己需要真正的循环引用时,也有一个解决方案。Python 为此提供了weakref模块。

A weak reference to an object is not enough to keep the object alive: when the only remaining references to a referent are weak references, garbage collection is free to destroy the referent and reuse its memory for something else. A primary use for weak references is to implement caches or mappings holding large objects, where it’s desired that a large object not be kept alive solely because it appears in a cache or mapping.

这是用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

在这个例子中,我使用 weakref.ref 在构造函数 FooType 中分配父引用。这是一个弱引用,因此它并没有真正创建一个循环。由于 GC 没有看到循环,它会正常回收两个对象。

结论

Python 通过 __del__ 方法完全可以使用析构方法,并适用于绝大多数的用例。一些不能普遍使用的场景,比如循环引用,又通常是不佳设计的标志。对于必须使用合理循环引用的少量使用情况,可以使用 weakref 提供的弱引用来破坏循环。

参考

Python destructor and garbage collection notes

How do I correctly clean up a Python object?

Cleaning up an internal pysqlite connection on object destruction

本文开发(python)相关术语:python基础教程 python多线程 web开发工程师 软件开发工程师 软件开发流程

代码区博客精选文章
分页:12
转载请注明
本文标题:Safely using destructors in Python
本站链接:https://www.codesec.net/view/628291.html


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 开发(python) | 评论(0) | 阅读(52)