Python基础: with模式和__enter__ 和 __exit__

一、说明

       有一些任务,可能事先需要设置,事后做清理工作。 with方法就是python的非常酷的语句,安全可靠,方便。我们自己的类如何具备with的能力?必须拥有__enter__()方法,另一个__exit__(),因此,这里介绍这些能力。

        对于这种场景,一个很好的例子是文件处理,你需要获取一个文件句柄,从文件中读取数据,然后关闭文件句柄。

二、python的with语句

2.1 有with语句和无的区别

        一个很好的例子是文件处理,你需要获取一个文件句柄,从文件中读取数据,然后关闭文件句柄。如果不用with语句,代码如下:

file = open("/tmp/foo.txt")
data = file.read()
file.close()

        这里有两个问题:

  •         1)可能忘记关闭文件句柄;
  •         2)文件读取数据发生异常,没有进行任何处理。

        下面是处理异常的加强版本:

file = open("/tmp/foo.txt")
try:data = file.read()
finally:file.close()

        虽然这段代码运行良好,但是太冗长了。这时候就是with一展身手的时候了。除了有更优雅的语法,with还可以很好的处理上下文环境产生的异常。下面是with版本的代码:

with open("/tmp /foo.txt") as file:data = file.read()

2.2 with如何工作?

        这看起来充满魔法,但不仅仅是魔法,Python对with的处理还很聪明。基本思想是with所求值的对象必须有一个__enter__()方法,一个__exit__()方法。

        紧跟with后面的语句被求值后,返回对象的__enter__()方法被调用,这个方法的返回值将被赋值给as后面的变量。当with后面的代码块全部被执行完之后,将调用前面返回对象的__exit__()方法。

        下面例子可以具体说明with如何工作:

#!/usr/bin/env python
# with_example01.pyclass Sample:def __enter__(self):print( "In __enter__()" )return "Foo"def __exit__(self, type, value, trace):print( "In __exit__()")def get_sample():return Sample()with get_sample() as sample:print ("sample:", sample)

        输出如下

bash-3.2$ ./with_example01.py
In __enter__()
sample: Foo
In __exit__()

        正如你看到的,

   __enter__()方法被执行
   __enter__()方法返回的值 - 这个例子中是"Foo",赋值给变量'sample'
        执行代码块,打印变量"sample"的值为 "Foo"
  __exit__()方法被调用

        with真正强大之处是它可以处理异常。可能你已经注意到Sample类的__exit__方法有三个参数val,type 和 trace。 这些参数在异常处理中相当有用。我们来改一下代码,看看具体如何工作的。

#!/usr/bin/env python
# with_example02.pyclass Sample:def __enter__(self):return selfdef __exit__(self, type, value, trace):print "type:", typeprint "value:", valueprint "trace:", tracedef do_something(self):bar = 1/0return bar + 10with Sample() as sample:sample.do_something()

        这个例子中,with后面的get_sample()变成了Sample()。这没有任何关系,只要紧跟with后面的语句所返回的对象有 __enter__()__exit__()方法即可。此例中,Sample()的__enter__()方法返回新创建的Sample对象,并赋值给变量sample。

代码执行后:

bash-3.2$ ./with_example02.py
type: <type 'exceptions.ZeroDivisionError'>
value: integer division or modulo by zero
trace: <traceback object at 0x1004a8128>
Traceback (most recent call last):File "./with_example02.py", line 19, in <module>sample.do_somet hing()File "./with_example02.py", line 15, in do_somethingbar = 1/0
ZeroDivisionError: integer division or modulo by zero

        实际上,在with后面的代码块抛出任何异常时,__exit__()方法被执行。正如例子所示,异常抛出时,与之关联的type,value和stack trace传给__exit__()方法,因此抛出的ZeroDivisionError异常被打印出来了。开发库时,清理资源,关闭文件等等操作,都可以放在__exit__方法当中。

        因此,Python的with语句是提供一个有效的机制,让代码更简练,同时在异常产生时,清理工作更简单。

三、举例:上下文管理协议(__enter__,__exit)

3.1、上下文管理协议

       正如上文中已经揭示的:为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法

  1. __enter__()会在with语句出现(实例化对象)时执行
  2. __exit__()会在with语句的代码块实行完毕才会执行

        首先我们将模仿python的open文件方法: 

class Open:def __init__(self,name):self.name = namedef __enter__(self):                 #在实例化打开文件时即触发,在with时触发print('执行__enter__')return self                    #return的self会赋值给f,相当于通过Open类实例化处对象fdef __exit__(self,exc_type,exc_val,exc_tb):    #在with中的代码块执行完毕才会触发print('执行__exit__')with Open('a.txt') as f:                #会触发enter  '执行__enter__',相当于--》f=Open('a.txt').__enter__()print(f)                              #<__main__.Open object at 0x01477270>print(f.name)                         #'a.txt'
print('*'*10)                           #先---'执行__exit__'#后---'*********'   Output:
---------------------------------------------
执行__enter__
<__main__.Open object at 0x000000000210B208>
a.txt
执行__exit__
**********
---------------------------------------------

3.2 在__exit__设定出错判别

        __exit__()中有三个参数分别代表异常类型,异常值和追溯信息,执行了__exit__则表示with语句执行完毕

        1、若__exit__返回值不为True,则:

         a、若with语句中没有异常,则程序正常执行

         b、若with语句中出现异常,则程序会执行到with中出错的语句并执行__exit__,然后程序终止,‘吐出’异常

class Open:def __init__(self,name):self.name = namedef __enter__(self):                        print('执行__enter__')return self                    def __exit__(self,exc_type,exc_val,exc_tb):    print('执行__exit__')print(exc_type)                #<class 'AttributeError'>print(exc_val)                #'Open' object has no attribute 'age'print(exc_tb)                #<traceback object at 0x0178F738>with Open('a.txt') as f:print(f)print(f.age)       #因为f对象没有age属性,则出现异常,程序执行到该句时将异常传递给__exit__的三个参数,并结束程序执行,报错print(f.name)        #该行语句后面的语句都不会执行,包括with语句的以外的语句也不会执行
print('*'*10) Output:
---------------------------------------------------------
执行__enter__print(f.age)
AttributeError: 'Open' object has no attribute 'age'
<__main__.Open object at 0x000000000257E4A8>
执行__exit__
<class 'AttributeError'>
'Open' object has no attribute 'age'
<traceback object at 0x0000000002583288>
---------------------------------------------------------

 

        2、若__exit__返回值为True,则:  a、若with语句中没有异常,则程序正常执行  b、若with语句中出现异常,则程序会执行到with中出错的语句并执行__exit__,‘吞掉’异常。然后with语句中剩下的语句不会执行,但是会继续执行with语句以外的语句

class Open:def __init__(self,name):self.name = namedef __enter__(self):                        print('执行__enter__')return self                    def __exit__(self,exc_type,exc_val,exc_tb):    print('执行__exit__')print(exc_type)                #<class 'AttributeError'>print(exc_val)                #'Open' object has no attribute 'age'print(exc_tb)                #<traceback object at 0x0178F738>return True
with Open('a.txt') as f:print(f)print(f.age)        #因为f对象没有age属性,则出现异常,程序执行到该句时将异常传递给__exit__的三个参数,并结束程序执行,'吞掉异常'不会报错print(f.name)       #该行语句后面的with中的语句都不会执行,但是with语句的以外的语句会继续执行
print('*'*10)            #'*********'

 

四、结论

  1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预 2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处。

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

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

相关文章

黑马JVM总结(五)

&#xff08;1&#xff09;方法区 它是所有java虚拟机 线程共享的区&#xff0c;存储着跟类的结构相关的信息&#xff0c;类的成员变量&#xff0c;方法数据&#xff0c;成员方法&#xff0c;构造器方法&#xff0c;特殊方法&#xff08;类的构造器&#xff09; 方法区在虚拟机…

【算法专题突破】双指针 - 最大连续1的个数 III(11)

目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后&#xff1a; 1. 题目解析 题目链接&#xff1a;1004. 最大连续1的个数 III - 力扣&#xff08;Leetcode&#xff09; 这道题不难理解&#xff0c;其实就是求出最长的连续是1的子数组&#xff0c; 但是&#xff0c;他支…

用vagrant快速创建linux虚拟机

参考B站&#xff1a;https://www.bilibili.com/video/BV1np4y1C7Yf 1、下载VirtualBox 2、下载vagrant 3、vagrant官网下载.box文件 官网&#xff1a;https://app.vagrantup.com/boxes/search 例如要下载这个centos/7 点进去&#xff0c;点击下载 下载后放到一个指定目录…

OSCP系列靶场-Esay-SunsetNoontide保姆级

OSCP系列靶场-Esay-SunsetNoontide 目录 OSCP系列靶场-Esay-SunsetNoontide总结准备工作信息收集-端口扫描目标开放端口收集目标端口对应服务探测 信息收集-端口测试chatgpt学习 漏洞利用-getwebshell漏洞利用-unrealircd 内网遨游-getshell交互shellFLAG1获取信息收集-内网基础…

typeScript--[interface接口实现类的定义,函数定义,可索引定义]

一.定义函数接口 interface Fn {(name: string): void; } var fn: Fn function (name): void {console.log(name); }; fn("逍遥的码农"); 二.定义类的接口 定义类需要用到关键字implements跟上定义的接口 interface Me {name: string;age: number;say(): void; }…

【C++进阶】二叉树进阶之二叉搜索树

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

Vector底层原理——面试之我答

Vector概述 vector是STL中最常用的容器&#xff0c;vector主要功能是作动态数组来弥补传统数组的缺点&#xff0c;如&#xff1a;不灵活&#xff0c;不方便插入等等。 Vector支持随机访问&#xff0c;因此访问某一个元素的时间复杂度是O(1)。 vector中存储着许多易用的函数方法…

C++与OPENCV实战学习

基于双目视觉的三维重建C实战_基于视觉sfmmvs的三维重建程序c源码(带gui界面)_新缸中之脑的博客-CSDN博客

逆市而行:如何在市场恐慌时保持冷静并抓住机会?

市场中的恐慌和波动是投资者所不可避免的。当市场出现恐慌情绪时&#xff0c;很多投资者会盲目跟从大众&#xff0c;导致决策出现错误。然而&#xff0c;聪明的投资者懂得在恐慌中保持冷静&#xff0c;并将其视为抓住机会的时机。本文将分享一些在市场恐慌时保持冷静并抓住机会…

Azure + React + ASP.NET Core 项目笔记一:项目环境搭建(二)

有意义的标题 pnpm 安装umi4 脚手架搭建打包语句变更Visual Studio调试Azure 设置变更发布 pnpm 安装 参考官网&#xff0c;或者直接使用npm安装 npm install -g pnpmumi4 脚手架搭建 我这里用的umi4&#xff0c;官网已附上 这里需要把clientapp清空&#xff0c;之后 cd Cl…

游戏性能优化

Unity性能优化主要包括以下方面&#xff1a; 1.渲染性能 。包括减少Draw Calls、减少三角面数、使用LOD、使用批处理技术、减少实时光源等&#xff0c;以提高游戏的帧率和渲染效率。 2.内存性能 。包括使用对象池、使用合适的纹理、使用异步加载资源等&#xff0c;以减少内存占…

【JAVA】String类

作者主页&#xff1a;paper jie_的博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《JAVASE语法系列》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和…

基于SSM的高校共享单车管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

Redis原理:IntSet

&#xff08;笔记总结自b站黑马程序员课程&#xff09; 一、结构 IntSet是Redis中set集合的一种实现方式&#xff0c;基于整数数组来实现&#xff0c;并且具备长度可变、有序等特征。 结构如下&#xff1a; typedef struct intset {uint32_t encoding; //编码方式uint32_t l…

读取XML的几种方式

一、为什么使用XML 1、便于不同应用程序之间通信。 2、便于不同平台之间通信。 3、便于不同平台之间数据共享。 二、Dom读取 xml文件内容 <?xml version"1.0" encoding"UTF-8"?> <bookstore><book id"1"><name>冰…

归并排序-面试例子

小数和问题 描述 在一个数组中&#xff0c;一个数左边比它小的数的总和&#xff0c;叫数的小和&#xff0c;所有数的小和累加起来&#xff0c;叫数组小和。求数组小和。 例子 5 2 6 1 7 小和原始的求法是&#xff1a;任何一个数左边比它小的数累加起来。 5左边比它小数累加…

面试系列 - Redis持久化机制详解

目录 一、Redis 持久化机制 二、混合使用 RDB 和 AOF 三、 RDB(Redis DataBase)详解 四、AOF&#xff08;Append-Only File&#xff09;详解 Redis 是一个内存数据库&#xff0c;为了持久化数据以确保数据不会在服务器重启时丢失&#xff0c;Redis 提供了两种主要的持久化机…

执行 JUnit 单元测试前,修改环境变量

同一份代码&#xff0c;在不改变配置文件的情况下&#xff0c;可以连接不同的数据库&#xff0c;进行JUnit测试。 非开发、测试、生产环境的区别。而是 我就站在这里&#xff0c;指哪打哪&#xff01; 避免重复造轮子&#xff0c;参考博文&#xff1a; 使用junit&spri…

python main 函数-启动-传递参数 python 打包 exe C# 进程传参

Part1:Python main 传递参数 在Python编程中&#xff0c;我们经常需要从命令行或其他外部环境中获取参数。Python提供了一种简单而灵活的方式来处理这些参数&#xff0c;即通过main函数传参 1.python main 函数-启动-传递参数 test.py import sysdef main():# 获取命令行参…

QT 自定义信号

自定义信号&#xff0c;需要 1.在singnals:区域下写信号函数&#xff0c;以及函数对应的参数 2. 需要emit关键字进行发射信号 3. 在需要处理该信号的其他类中&#xff0c;建立信号和其信号槽函数connect() 4. 在其他类中创建信号处理槽函数 #include "mythread.h"my…