关于使用 Python 析构函数的正确姿势

2019 年 11 月 18 日

关于使用 Python 析构函数的正确姿势

python 在大家的印象中,没有专用的构造和析构函数。但是,从现在开始,作者将带领大家熟悉 python 中的__init__和__del__函数,以替代构造和析构机制。


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


而在 Python 中没有专用的构造和析构函数,但是一般可以在__init__和__del__分别完成初始化和删除操作,以替代构造和析构。


但是 Python 社区中的许多人都不推荐使用 del,因为 Python 对对象使用了引用计数来管理,很多情况下是很难以估计是什么时候引用计数为 0 而造成销毁的,同时很多使用技巧告诉我们使用 Python 编程不用再过度优化内存使用,以避免写出 C++ 风格的代码。


在本文中,我们将明确如何来正确使用__del__。


1 举个栗子


我们先来看个简单的测试用例:



输出:



Python 里也同 Java 一样采用了垃圾收集机制,不过不一样的是:


Python 采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略。因此,当 Python 在超出范围时不会清理它,只有当它的最后一次引用超出范围时,才会将它清理掉。


如下测试所示:



输出:



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


2 上下文管理器


刚开始写 Python 时,我很长一段时间都在用 Python 写 C++,直到当我开始逐渐了解习惯并喜欢上使用库以及更多高级概念 (generators, decorators, contexts, etc) 时,我的 Python 编程技巧才得以提升。


Python 提供了一种更好的方法上下文来管理资源 contexts。上下文管理器允许你在有需要的时候,精确地分配和释放资源。 使用上下文管理器最广泛的案例就是 with 语句了。例如,处理写入文件时的最佳方法是:



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


不过,如果您的业务场景需要封装了某种类型的数据库,该对象必须在结束时提交并关闭。假设对象是某个大型复杂类的成员变量,父对象在不同的方法中需要不时与 DB 对象交互,那么在这里使用 with 是不实际的,我们则需要一个功能完备的析构函数来帮我们完成相关资源的处理及释放。


3 析构函数和计数垃圾收集器


为了解决我在上一段中提到的问题,我们要开始考虑如何有效使用__del__析构函数。首先需要面对的就是解决引用计数垃圾收集器同循环引用之间的问题,举个例子:



输出



emmm…为什么我的 del 不执行呢??


以下是 Python 官方文档在此问题上的说法:



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


4 解决方案


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


不过析构函数也是可以在 Python 中被安全使用的,注意点可以概括为以下几点:


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


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


3.Dependency injection 可以有效防止复杂代码中的循环引用,但是当您发现自己的确需要真正的循环引用实现业务逻辑时,weakref 模块了解一下。



这是用 weakref 重写的前一个例子:



输出:



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


5 结论


Python 通过 del 方法完全可以使用析构方法,并适用于绝大多数的用例。一些不能普遍使用的场景,比如通常代表是不佳程序设计的循环引用。对于一些必须使用合理循环引用的情况,可以使用 weakref 提供的弱引用来破坏循环以保证 del 正确执行。


本文转载自公众号 360 云计算(ID:hulktalk)。


原文链接:


https://mp.weixin.qq.com/s/d2_MTvfbOFZmppej5UOwUw


2019 年 11 月 18 日 17:41480

评论

发布
暂无评论
发现更多内容

elasticsearch-restful-api笔记

wkq2786130

elasticsearch

百万并发「零拷贝」技术系列之初探门径

码农神说

Java 架构 零拷贝

架构师训练营第七课总结

曾祥斌

记一次bem命名规范使用优化方案

前端有的玩

Vue npm React bem

蚂蚁金服上市了,我不想努力了

YourBatman

IPO 财务自由 蚂蚁金服 财富自由

专访英特尔唐炯:对旗下产品性能及未来路线图充满信心

飞天鱼2017

jqGrid表格封装和使用方法

Seven_xw1213

Java 前端 封装 jqgrid

职业吐槽与反思(一)

石君

职场 吐槽

canal 笔记

wkq2786130

MySQL canal

一张PDF了解JDK11 GC调优秘籍-附PDF下载

程序那些事

Java jdk GC 秘籍 JDK11

手撕设计模式

Peision

Java 后端 设计模式 23种设计模式

GoF设计模式 | 单例模式

Peision

Java 后端 23种设计模式

前后端统一结果集封装

Peision

Java json 前后端分离 springboot

创业使人成长系列 (5)-申请国家高新企业

石云升

高新企业

性能优化-架构师体现技术全面性的时刻

LEAF

GoF设计模式 | 工厂方法模式

Peision

Java 23种设计模式

Java的异常处理

Bruce Duan

java异常处理

C++ 线程安全的单例模式总结

小林coding

c++ 设计模式 单例模式 线程安全

jvm-config

wkq2786130

Java JVM

JVM性能调优监控工具 jps jstat jinfo jmap jhat jstack

wkq2786130

Java JVM

Cmder 使用 笔记

wkq2786130

cmder tools

写在《SRE生存指南》出版之际

Winfield

DevOps SRE

玩转混合加密 | 精美配图

阿宝哥

安全 加密解密 数据加密

【译文】创建 Kubernetes manifest 的初学者指南

FeiLong

Kubernetes

OrientDB etl 工具 导入 rdbms数据

wkq2786130

为什么 Flink 无法实时写入 MySQL?

Apache Flink

flink

解决 Harbor 启动失败故障

FeiLong

Docker Harbor Docker-compose

vcenter 5.5故障处理

小小文

vcenter

neo4j 批量 导入 数据 的 几种方式

wkq2786130

neo4j

架构师训练营第七周作业

王铭铭

前后端分离跨域问题解决方案

Peision

Java 前后端分离 springboot

关于使用 Python 析构函数的正确姿势-InfoQ