Python基础: with模式和__enter__ 和 __exit__

news2024/11/20 3:36:48

一、说明

       有一些任务,可能事先需要设置,事后做清理工作。 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.py

class 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.py


class Sample:
    def __enter__(self):
        return self

    def __exit__(self, type, value, trace):
        print "type:", type
        print "value:", value
        print "trace:", trace

    def do_something(self):
        bar = 1/0
        return bar + 10

with 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_something
    bar = 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语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法

  1. __enter__()会在with语句出现(实例化对象)时执行
  2. __exit__()会在with语句的代码块实行完毕才会执行
class Open:
   def __init__(self,name):
          self.name = name
  
   def __enter__(self):                 #在实例化打开文件时即触发,在with时触发
         print('执行__enter__')
         return self                    #return的self会赋值给f,相当于通过Open类实例化处对象f

   def __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 = name
  
    def __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 = name
  
    def __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.coloradmin.cn/o/1000907.html

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

相关文章

Java特性之设计模式【建造者模式】

一、建造者模式 概述 建造者模式&#xff08;Builder Pattern&#xff09;使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式 一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其…

叔本华治愈了年轻人的精神内耗,拥抱甜甜的幸福

叔本华不姓叔。 叔本华是一位孤僻的糟老头子&#xff0c;但是他有一颗睿智的头脑。 叔本华是唯意志论的创始人和主要代表之一&#xff0c;认为生命意志是主宰世界运作的力量。 “人生就像钟摆&#xff0c;在痛苦和无聊之中摆荡。” 欲望得到满足就无聊&#xff0c;欲望没有满…

防火墙 FireWall

这里写自定义目录标题 一、概述二、防火墙分类三、防火墙性能四、硬件防火墙定义五、硬件防火墙作用&#xff08;拓扑图 ups&#xff09;六、硬件防火墙品牌七、软件防火墙八、iptables一、iptables是什么&#xff1f;二、netfilter/iptables功能三、iptables概念四、iptables中…

图像处理:双边滤波

1. 双边滤波 公式含义: q:输入的像素点,它代表上图中55的方框中其中的一个像素点 : 空间域核 : 图像像素域核 (两个二维高斯函数,二维高斯函数的公式为:) 进行了一些小的改动,让他们更加符合我们的要求,这里给出

字符检测专题第一期:OCR技术工业应用浅谈

难题不会做&#xff1f;扫一扫&#xff0c;题目、解析立马出现。寄快递需要输入信息&#xff1f;扫一扫&#xff0c;软件自动提取上传。身份证信息需要录入&#xff1f;扫一扫&#xff0c;立马精准识别。这些都是我们日常司空见惯的动作&#xff0c;而实现这一切正是得益于OCR技…

(其他) 剑指 Offer 61. 扑克牌中的顺子 ——【Leetcode每日一题】

❓剑指 Offer 61. 扑克牌中的顺子 难度&#xff1a;简单 从若干副扑克牌中随机抽 5 张牌&#xff0c;判断是不是一个顺子&#xff0c;即这5张牌是不是连续的。2&#xff5e;10为数字本身&#xff0c;A为1&#xff0c;J为11&#xff0c;Q为12&#xff0c;K为13&#xff0c;而大…

一分钟图情论文:《原始的布拉德福定律》

天津大学图书馆的研究馆员范铮先生&#xff0c;在《图书情报工作》第一期中发表了题为《原始的布拉德福定律》的文章&#xff0c;详细介绍了布拉德福定律的历史背景、调查统计数据、文献曲线以及理论推导等关键内容。这篇文章让我们能够深入了解布拉德福定律的本质和原始构想。…

linux内核源码分析之虚拟内存

目录 虚拟地址空间划分&#xff08;用户空间&#xff09; 32位系统虚拟地址空间分配 64位系统虚拟地址空间分配 内存管理 内核布局虚拟地址空间 虚拟内存区域在内核中组织 内存访问权限 调用malloc 申请内存&#xff0c; 虚拟地址空间布局&#xff08;内核&#xff09…

动静态库生成使用

&#x1f525;&#x1f525; 欢迎来到小林的博客&#xff01;&#xff01;       &#x1f6f0;️博客主页&#xff1a;✈️林 子       &#x1f6f0;️博客专栏&#xff1a;✈️ Linux       &#x1f6f0;️社区 :✈️ 进步学堂       &#x1f6f0…

2000-2022年上市公司行业异质性数据(技术密集型、劳动密集型、资本密集型)(含原始数据和处理代码)

2000-2022年上市公司行业异质性数据&#xff08;技术密集型、劳动密集型、资本密集型&#xff09;&#xff08;含原始数据和处理代码&#xff09; 1、时间&#xff1a;2000-2022年 2、指标&#xff1a;股票代码、年份、股票简称、统计日期、行业名称、行业代码、成立日期、上…

React复习日志大纲

文章目录 创建项目启动项目项目目录说明调整项目src剩余目录01基本使用02 列表渲染03 条件渲染04 样式处理05 函数和类组件创建和渲染06 事件绑定07 事件对象e08 传递额外参数09 组件状态修改10 受控组件11 非受控组件12 组件通信父传子13 Props说明14 组件通信子传父15 组件通…

【已解决】您所使用的密钥ak有问题,不支持jsapi服务,可以访问该网址了解如何获取有效密钥。

您所使用的密钥ak有问题&#xff0c;不支持jsapi服务&#xff0c;可以访问该网址了解如何获取有效密钥。详情查看&#xff1a;http://lbsyun.baidu.com/apiconsole/key#。 问题 百度密钥过期 思路 注册成为开发者 如果还没注册百度地图api账号的&#xff0c;点击以后就进入…

LRU算法之我见

文章目录 一、LRU算法是什么&#xff1f;二、使用原理三、代码实现总结 一、LRU算法是什么&#xff1f; LRU算法又称最近最少使用算法&#xff0c;它是是大部分操作系统为最大化页面命中率而广泛采用的一种页面置换算法。是一种缓存淘汰策略&#xff0c;根据使用频率来淘汰无用…

华清远见第六课程day4作业

仿照string类&#xff0c;完成myString 类 #include <iostream> #include <cstring>using namespace std;class myString{ private:char *str;int size; public:myString():size(10){str new char[size];strcpy(str,"");}myString(const char*s){size …

mysql leetcode打题记录

文章目录 完成度基本语法高级语法连接日期 函数编写函数聚合函数 因为上过的数据库课实在太水了&#xff0c;所以打算先在菜鸟教程/CSDN/leetcode先学一下基本语法&#xff0c;然后去做Stanford数据库原理的课程CS145。 小目标&#xff1a;把leetcode上不用钱的mysql的题先做一…

RabbitMQ死信队列与延迟队列

死信队列 死信队列的定义 死信队列&#xff08;Dead Letter Queue&#xff09;&#xff1a; 死信队列是一种特殊的队列&#xff0c;用于存放不能被消费的消息。当消息满足某些条件时&#xff0c;比如消息过期、消息被拒绝消费或消息达到最大重试次数等&#xff0c;RabbitMQ 会…

实现数组去重的七种方法

实现数组去重的 7 种方式 1. 方法一&#xff1a;利用两层循环数组的splice方法 通过两层循环对数组元素进行逐一比较&#xff0c;然后通过splice方法来删除重复的元素。此方法对NaN是无法进行去重的&#xff0c;因为进行比较时NaN ! NaN。 let arr [1, 2, 2, abc, abc, true,…

Mac系统 AndroidStudio Missing essential plugin:org.jetbrains.android报错

打开Android Studio,提示 Missing essential plugin:org.jetbrains.android错误&#xff0c;产生的原因是Kotlin被禁用。 解决的方法是删除disabled_plugins.txt&#xff0c;Mac OS对应的路径为&#xff1a; /Users/xzh/Library/Application Support/Google/AndroidStudio202…

C#中async/await的线程ID变化情况

一、简单的起步 Console.WriteLine($"主线程开始ID&#xff1a;{Thread.CurrentThread.ManagedThreadId}");//aawait Task.Delay(100);//cConsole.WriteLine($"主线程结束ID&#xff1a;{Environment.CurrentManagedThreadId}");//b 结果&#xff1a; …

MySQL复合查询(查询直接看这里)

回顾基本查询 查询工资高于500或岗位为TOM的雇员&#xff0c;同时还要满足他们的姓名首字母为大写的J select * from EMP where(sale > 500 or job TOM) and ename like J%;按照部门号升序而雇员的工资降序排序 select * from EMP order by deptno, sal desc;最后&#…