《Python面向对象编程指南》——2.7 __del__()方法

本节书摘来自异步社区《Python面向对象编程指南》一书中的第2章,第2.7节,作者[美]Steven F. Lott, 张心韬 兰亮 译,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.7 __del__()方法

__del__()方法有一个让人费解的使用场景。

这个方法的目的是在将一个对象从内存中清除之前,可以有机会做一些清理工作。如果使用上下文管理对象或者with语句来处理这种需求会更加清晰,这也是第5章“可调用对象和上下文的使用”的内容。对于Python的垃圾回收机制而言,创建一个上下文比使用__del__()更加容易预判。

但是,如果一个Python对象包含了一些操作系统的资源,__del__()方法是把资源从程序中释放的最后机会。例如,引用了一个打开的文件、安装好的设备或者子进程的对象,如果我们将资源释放作为__del__()方法的一部分实现,那么我们就可以保证这些资源最后会被释放。

很难预测什么时候__del__()方法会被调用。它并不总是在使用del语句删除对象时被调用,当一个对象因为命名空间被移除而被删除时,它也不一定被调用。Python文档中用不稳定来描述__del__()方法的这种行为,并且提供了额外的关于异常处理的注释:运行期的异常会被忽略,相对地,会使用sys.stderr打印一个警告。

基于上面的这些原因,通常更倾向于使用上下文管理器,而不是实现__del__()。

2.7.1 引用计数和对象销毁

CPython的实现中,对象会包括一个引用计数器。当对象被赋值给一个变量时,这个计数器会递增;当变量被删除时,这个计数器会递减。当引用计数器的值为0时,表示我们的程序不再需要这个对象并且可以销毁这个对象。对于简单对象,当执行删除对象的操作时会调用__del__()方法。

对于包含循环引用的复杂对象,引用计数器有可能永远也不会归零,这样就很难让__del__()被调用。

我们用下面的一个类来看看这个过程中到底发生了什么。

class Noisy:def __del__( self ):print( "Removing {0}".format(id(self)) )

我们可以像下面这样创建和删除这个对象。

>>> x= Noisy()
>>>del x
Removing 4313946640

我们先创建,然后删除了Noisy对象,几乎是立刻就看到了__del__()方法中输出的消息。这也就是说当变量x被删除后,引用计数器正确地归零了。一旦变量被删除,就没有任何地方引用Noisy实例,所以它也可以被清除。

下面是浅复制中一种常见的情形。

>>> ln = [ Noisy(), Noisy() ]
>>> ln2= ln[:]
>>> del ln

Python没有响应del语句。这说明这些Noisy对象的引用计数器还没有归零,肯定还有其他地方引用了它们,下面的代码验证了这一点。

>>> del ln2
Removing 4313920336
Removing 4313920208

ln2变量是ln列表的一个浅复制。有两个列表引用了Noisy对象,所以在这两个列表被删除并且引用计数器归零之前,Python不会销毁这两个Noisy对象。

还有很多种创建浅复制的方法。下面是其中的一些。

a = b = Noisy()
c = [ Noisy() ] * 2

这里的关键是,由于浅复制在Python中非常普遍,所以我们往往对存在的对象的引用感到非常困惑。

2.7.2 循环引用和垃圾回收

下面是一种常见的循环引用的情形。一个父类包含一个子类的集合,同时集合中的每个子类实例又包含父类的引用。

下面我们用这两个类来看看循环引用。

class Parent:def __init__( self, *children ):self.children= list(children)for child in self.children:child.parent= selfdef __del__( self ):print( "Removing {__class__.__name__} {id:d}".
format( __class__=self.__class__, id=id(self)) )
class Child:def __del__( self ):print( "Removing {__class__.__name__} {id:d}".
format( __class__=self.__class__, id=id(self)) )

一个Parent的instance包括一个children的列表。

每一个Child的实例都有一个指向Parent类的引用。当向Parent内部的集合中插入新的Child实例时,这个引用就会被创建。

我们故意把这两个类写得比较复杂,所以下面让我们看看当试图删除对象时,会发生什么。

>>>> p = Parent( Child(), Child() )
>>> id(p)
4313921808
>>> del p

Parent和它的两个初始Child实例都不能被删除,因为它们之间互相引用。

下面,我们创建一个没有Child集合的Parent实例。

>>> p= Parent()
>>> id(p)
4313921744
>>> del p
Removing Parent 4313921744

和我们预期的一样,这个Parent实例成功地被删除了。

由于互相之间有引用存在,因此我们不能从内存中删除Parent实例和它包含的Child实例的集合。如果我们导入垃圾回收器的接口——gc,我们就可以回收和显示这些不能被删除的对象。

下面的代码中,我们使用了gc.collect()方法回收所有定义了__del__()方法但是无法被删除的对象。

>>> import gc
>>> gc.collect()
174
>>> gc.garbage
[<__main__.Parent object at 0x101213910>, <__main__.Child object at 0x101213890>, <__main__.Child object at 0x101213650>, <__main__.Parent object at 0x101213850>, <__main__.Child object at 0x1012130d0>, <__main__.Child object at 0x101219a10>, <__main__.Parent object at 0x101213250>, <__main__.Child object at 0x101213090>, <__main__.Child object at 0x101219810>, <__main__.Parent object at 0x101213050>, <__main__.Child object at 0x101213210>, <__main__.Child object at 0x101219f90>, <__main__.Parent object at 0x101213810>, <__main__.Child object at 0x1012137d0>, <__main__.Child object at 0x101213790>]

可以看到,我们的Parent对象(例如,4313921808的ID = 0x101213910)在不可删除的垃圾对象列表中很突出。为了让引用计数器归零,我们需要删除所有Parent对象中的children列表,或者删除所有Child实例中对Parent的引用。

注意,即使把清理资源的代码放在__del__()方法中,我们也没办法解决循环引用的问题。因为__del__()方法是在循环引用被解除并且引用计数器已经归零之后被调用的。当有循环引用时,我们不能只是简单地依赖于Python中计算引用数量的机制来清理内存中的无用对象。我们必须显式地解除循环引用或者使用可以保证垃圾回收的weakref引用。

2.7.3 循环引用和weakref模块

如果我们需要循环引用,但是又希望将清理资源的代码写在__del__()中,这时候我们可以使用弱引用。循环引用的一个常见场景是互相引用:一个父类中包含了一个集合,集合中的每一个实例也包含了一个指向父类的引用。如果一个Player对象中包含多个Hand实例,那么在每一个Hand对象中都包括一个指向对应的Player类的引用可能会更方便。

默认的对象间的引用可以被称为强引用,但是,叫直接引用可能更好。Python的引用计数机制会直接使用它们,而且如果引用计数无法删除这些对象的话,垃圾回收机器也能及时发现。它们是不可忽略的对象。

对一个对象的强引用就是直接引用,下面是一个例子。

当我们遇到如下语句。

a= B()

变量a直接引用了B类的一个对象。此时B的引用计数至少是1,因为a变量包含了一个指向它的引用。

想要找个一个弱引用相关的对象需要两个步骤。一个弱引用会调用x.parent(),这个函数将弱引用作为一个可调用对象来查找它真正的父对象。这个过程让引用计数器得以归零,垃圾回收器可以回收引用的对象,但是不回收这个弱引用。

weakref定义了一系列使用了弱引用而没有使用强引用的集合。它让我们可以创建一种特殊的字典类型,当这种字典的对象没有用时,可以保证被垃圾回收。

我们可以修改Parent和Child类,在Child指向Parent的引用中使用弱引用,这样就可以简单地保证无用对象会被销毁。

下面是修改后的类,它在Child指向Parent的引用中使用了弱引用。

import weakref
class Parent2:def __init__( self, *children ):self.children= list(children)for child in self.children:child.parent= weakref.ref(self)def __del__( self ):print( "Removing {__class__.__name__} {id:d}".format( __class__= self.__class__, id=id(self)) )

我们将child中的parent引用改为一个weakref对象的引用。

在Child类中,我们必须用上面说的两步操作来定位parent对象:

p = self.parent()
if p is not None:# process p, the Parent instance
else:# the parent instance was garbage collected.

我们可以显式地确认引用的对象是否已经找到,因为有可能该引用已经变成虚引用。

当我们使用这个新的Parent2类时,可以看到引用计数成功地归零同时对象也被删除了:

>>> p = Parent2( Child(), Child() )
>>> del p
Removing Parent2 4303253584
Removing Child 4303256464
Removing Child 4303043344

当一个weakref引用变成死引用时(因为引用被销毁了),我们有3个可能的方案。

  • 重新创建引用对象,或重新从数据库中加载。
  • 当垃圾回收器在低内存情况下错误地删除了一些对象时,使用warnings模块记录调试信息。
  • 忽略这个问题。

通常,weakref引用变成死引用是因为响应的对象已经被删除了。例如,变量的作用域已经执行结束,一个没有用的命名空间,应用程序正在关闭。对于这个原因,通常我们会采取第3种响应方法。因为试图创建这个引用的对象时很可能马上就会被删除。

2.7.4 __del__()和close()方法

__del__()最常见的用途是确保文件被关闭。

通常,包含文件操作的类都会有类似下面这样的代码。

__del__ = close

这会保证__del__()方法同时也是close()方法。

其他更复杂的情况最好使用上下文管理器。详情请看第5章“可调用对象和上下文的使用”,我们会在第5章提供更多和上下文管理器有关的信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/541458.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NullReferenceException C#中的异常

什么是NullReferenceException&#xff1f; (What is NullReferenceException?) NullReferenceException is an exception and it throws when the code is trying to access a reference that is not referencing to any object. If a reference variable/object is not refe…

java map key 大写转小写_Spring JdbcTemplate 查询出的Map,是如何产生大小写忽略的Key的?(转)...

Java 是区分大小写的&#xff0c;普通的Map例如HashMap如果其中的key"ABC" value"XXX"那么map.get("Abc") 或 map.get("abc")是获取不到值得。但Spring中产生了一个忽略大小写的map使我产生了好奇例如 jdbcTemplate.queryForList(sql)…

《iOS 6核心开发手册(第4版)》——2.11节秘诀:构建星星滑块

本节书摘来自异步社区《iOS 6核心开发手册&#xff08;第4版&#xff09;》一书中的第2章&#xff0c;第2.11节秘诀&#xff1a;构建星星滑块&#xff0c;作者 【美】Erica Sadun&#xff0c;更多章节内容可以访问云栖社区“异步社区”公众号查看 2.11 秘诀&#xff1a;构建星星…

css框架和js框架_优雅设计的顶级CSS框架

css框架和js框架Brief discussion: 简要讨论&#xff1a; Well, who doesnt want their website or web page to look attractive, stylish and be responsive? 那么&#xff0c;谁不希望自己的网站或网页看起来有吸引力&#xff0c;时尚并且ReactSwift&#xff1f; We put …

软考下午题具体解释---数据流图设计

在历年的软考下午题其中&#xff0c;有五道大题。各自是数据流图的设计&#xff0c;数据库设计&#xff0c;uml图&#xff0c;算法和设计模式&#xff0c;从今天这篇博文開始&#xff0c;小编就跟大家来一起学习软考下午题的相关内容。包含理论上的知识以及典型例题的解说&…

基本程序 打印Scala的Hello World

Scala中的基本程序 (Basic program in Scala) As your first Scala program, we will see a basic output program that just prints "Hello World" or any other similar type of string. With this example, we will see what are the part of the code that is im…

java treemap lastkey_Java TreeMap lastKey()用法及代码示例

java.util.TreeMap.lastKey()用于检索Map中存在的最后一个或最高键。用法:tree_map.lastKey()参数&#xff1a;该方法不带任何参数。返回值&#xff1a;该方法返回映射中存在的最后一个键。异常&#xff1a;如果映射为空&#xff0c;则该方法将引发NoSuchElementException。以下…

mysql属于数据库三级模式_数据库系统的三级模式指的是什么

数据库系统的三级模式指的是什么发布时间&#xff1a;2020-10-26 10:11:21来源&#xff1a;亿速云阅读&#xff1a;52作者&#xff1a;小新小编给大家分享一下数据库系统的三级模式指的是什么&#xff0c;希望大家阅读完这篇文章后大所收获&#xff0c;下面让我们一起去探讨吧&…

《自顶向下网络设计(第3版)》——导读

目录 第1部分 辨明客户的需求和目标 第1章 分析商业目标和制约 1.1 采用自顶向下的网络设计方法 1.2 分析商业目标 1.3 分析商业制约 1.4 商业目标检查表 1.5 小结 1.6 复习题 1.7 设计环境 第2章 分析技术目标与折衷措施 2.1 可扩展性 2.2 可用性 2.3 网络性能 2.4 安全性 2…

python矩阵变化_用numpy改变矩阵的形状

我的问题有两个方面。我有下面的代码来处理一些矩阵。在import numpytupleList [(0, 122), (1, 246), (2, 157), (3, 166), (4, 315), (5, 108), (6, 172), (7, 20), (8, 173), (9, 38), (10, 28), (11, 72), (12, 102), (13, 277), (14, 318), (15, 316), (16, 283), (17, 31…

最小硬币问题_进行更改的最小硬币数量

最小硬币问题Description: 描述&#xff1a; This is classic dynamic programming problem to find minimum number of coins to make a change. This problem has been featured in interview rounds of Amazon, Morgan Stanley, Paytm, Samsung etc. 这是经典的动态编程问题…

java 生成xml乱码_jdom解决中文乱码问题 JAVA生成xml文件帮了我很大的忙

决解了数据库读取出来 再保存到xml 产生的乱码问题import java.io.FileOutputStream;import java.io.IOException;import java.io.OutputStreamWriter;import org.jdom.Attribute;import org.jdom.Document;import org.jdom.Element;import org.jdom.output.Format;import org.…

给定重量上限,背包问题_满足给定重量的袋子的最低成本

给定重量上限,背包问题Problem statement: 问题陈述&#xff1a; You are given a bag of size W kg and you are provided costs of packets different weights of oranges in array cost[] where cost[i] is basically cost of i kg packet of oranges. cost[i] -1 means t…

springMVC rest风格

1.dispatcherServlet的配置<!-- The front controller of this Spring Web application, responsible for handling all application requests --><servlet><servlet-name>springDispatcherServlet</servlet-name><servlet-class>org.springfram…

sql2008能否打开mysql数据库_mysql数据库数据能不能导入到sql server中

点“测试”按钮确认你的链接是正确的。 Press the "Test" button to ensure your connection settings are set properly and then the "OK" button when youre done.二. 创建Microsoft SQL到MySQL的链接1.在SQL Server Management Studio中打开一个new qu…

c语言 函数的参数传递示例_isunordered()函数与C ++中的示例

c语言 函数的参数传递示例C isunordered()函数 (C isunordered() function) isunordered() function is a library function of cmath header, it is used to check whether the given values are unordered (if one or both values are Not-A-Number (NaN)), then they are u…

java进一_JAVA小白进:基础入门知识

1.注释&#xff0c;关键字&#xff0c;标识符1.注释(1)注释&#xff1a;解释说明程序的而文字。(2)注释的分类&#xff1a;单行注释 格式&#xff1a; //注释的文字多行注释 格式&#xff1a;/*注释的文字*/文档注释 格式&#xff1a;/**注释的文字*/(3)注释的作用&#xff1a;…

补丁(patch)的制作与应用

为什么80%的码农都做不了架构师&#xff1f;>>> 转自http://linux-wiki.cn/wiki/zh-hans/%E8%A1%A5%E4%B8%81(patch)%E7%9A%84%E5%88%B6%E4%BD%9C%E4%B8%8E%E5%BA%94%E7%94%A8 如果hack了开源代码&#xff0c;为了方便分享&#xff08;如提交Bug&#xff09;或自己…

php知识点汇总与解答_PHP操作员能力倾向问题与解答

php知识点汇总与解答This section contains Aptitude Questions and Answers on PHP Operators. 本节包含有关PHP运算符的 Aptitude问答。 1) Which of the following types of operators are used in PHP? Arithmetic OperatorsLogical OperatorsArray OperatorsString Oper…

csv导入mysql phpmyadmin_【转】从phpMyAdmin批量导入Excel内容到MySQL(亲测非常简洁有效)...

今天做项目遇到需要用phpMyAdmin批量导入Excel内容到MySQL数据库。分析了我的踏坑经历并且总结一最便捷的一套导入数据的方法&#xff0c;非常实用简洁&#xff1a;1、修改Excel表的数据&#xff0c;使得Excel中的字段与数据库字段要一一对应&#xff0c;并加上自增id。2、然后…