【Python】上下文管理协议__enter__和__exit__函数

部分程序中,可能事先要准备资源,事后做清理工作。 with方法就是python的非常酷的语句,安全可靠,方便。我们自己的类如何具备with的能力?必须拥有__enter__()函数和一个__exit__()函数,本章节介绍两个函数的能力。

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

一、with语句

1,文件读取操作,通常打开一个文件,然后读取文件内容,最后需要关闭文件,代码如下:

python 复制代码
f = open("./demofile2",mode="r",encoding="UTF-8")
for content in f:
    print(content)
f.close()

这样的程序文件打开正常时没问题,但是存在2个问题

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

2,使用with语句如下:

python 复制代码
with open("./demofile2",mode="r",encoding="UTF-8") as f:

    data = f.read()

# 离开with语句文件自动关闭
print(data)

3,with语句执行过程

with操作很魔法,Python对with的处理还很聪明。基本思想是with所求值的对象必须有一个__enter__()方法,一个__exit__()方法。

执行过程概述,with后面的语句被执行后,enter()函数被调用,enter_()的返回值将被赋值给as后面的变量,后边详细说明_enter__()函数返回值问题。当with后面的代码块全部被执行完之后,将调用_enter__()返回对象的__exit__()方法。

函数及参数说明:

  • enter(self):当with开始运行的时候触发此方法的运行
  • exit(self, exc_type, exc_val, exc_tb):当with运行结束之后触发此方法的运行
  • exc_type如果抛出异常,这里获取异常的类型
  • exc_val如果抛出异常,这里显示异常内容
  • exc_tb如果抛出异常,这里显示所在位置

4,代码示例

python 复制代码
class ConvertType(object):

    def __enter__(self):
        print("Entry with!")

        
    def __exit__(self,type,value,trace):
        print(f"type:{type}")
        print(f"value:{value}")
        print(f"trace:{trace}")
        print("Exit with!")
        
    def strToInt(self,targetString:str) -> int:
        return int(targetString)


with  ConvertType() as f:
    print(f)
    f.strToInt("1343f")

print("Done!")
    

4.1 执行结果

首先预测一下代码能否正常执行,尝试执行结果如下:

python 复制代码
Entry with!
None
type:<class 'AttributeError'>
value:'NoneType' object has no attribute 'strToInt'
trace:<traceback object at 0x1110a6b80>
Exit with!
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[30], line 21
     19 with  ConvertType() as f:
     20     print(f)
---> 21     f.strToInt("1343f")
     23 print("Done!")

AttributeError: 'NoneType' object has no attribute 'strToInt'

报错分析:

发现print(f)打印为:None ,自然无法调用strToInt函数,也就是没有进入with...as代码块,原因如下:

with 语句执行时调用__enter__函数返回的对象成为f,这里__enter__函数没有return语句,默认返回None,因此f就是None

因此需要__enter__函数返回实例对象,修改代码如下:

python 复制代码
class ConvertType(object):

    def __enter__(self):
        print("Entry with!")
        return self #新增一行返回值对象本身self
        
    def __exit__(self,type,value,trace):
        print(f"type:{type}")
        print(f"value:{value}")
        print(f"trace:{trace}")
        print("Exit with!")
        
    def strToInt(self,targetString:str) -> int:
        return int(targetString)


with  ConvertType() as f:
    print(f)
    f.strToInt("1343f")

print("Done!")

4.2 __enter__函数加返回值执行结果

python 复制代码
Entry with!
<__main__.ConvertType object at 0x110fca000>
type:<class 'ValueError'>
value:invalid literal for int() with base 10: '1343f'
trace:<traceback object at 0x111070500>
Exit with!
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[31], line 21
     19 with  ConvertType() as f:
     20     print(f)
---> 21     f.strToInt("1343f")
     23 print("Done!")

Cell In[31], line 16, in ConvertType.strToInt(self, targetString)
     15 def strToInt(self,targetString:str) -> int:
---> 16     return int(targetString)

ValueError: invalid literal for int() with base 10: '1343f'

报错分析:

发现程序已经执行到了with...as代码块,在执行f.strToInt函数时报错了,原因是传入的参数"1343f"无法转化为int类型。另外我们还发现在with...as代码块执行失败后,最后一行代码print("Done!")没有被执行。

原因是__exit__函数没有返回值,在没有返回值的情况下,with...as代码块执行失败后,代码块程序会中断,即剩余代码块程序不会被执行,直接执行__exit__函数,__exit__函数无返回值的情况下会将错误暴露出来,如上执行结果。

那么给__exit__函数增加返回值"True",修改代码:

python 复制代码
class ConvertType(object):

    def __enter__(self):
        print("Entry with!")
        return self
        
    def __exit__(self,type,value,trace):
        print(f"type:{type}")
        print(f"value:{value}")
        print(f"trace:{trace}")
        print("Exit with!")
        return True #新增一行返回值True
        
    def strToInt(self,targetString:str) -> int:
        return int(targetString)


with  ConvertType() as f:
    print(f)
    f.strToInt("1343f")

print("Done!")

4.3 __exit__函数加返回值后执行结果

python 复制代码
Entry with!
<__main__.ConvertType object at 0x1102c9c40>
type:<class 'ValueError'>
value:invalid literal for int() with base 10: '1343f'
trace:<traceback object at 0x1105d2180>
Exit with!
Done!

执行结果分析:

同样是参数"1343f"执行strToInt函数报错,但是错误信息被"吞掉",没有暴露出来,而with...as语句块之外的剩余语句被执行,输出Done!

说明在__exit__函数返回True时,执行with...as代码块发生异常时会包装异常,执行会中断当前with...as语句块,然后执行执行__exit__函数,最后with语句以外的代码。

4.4 正常执行测试

给定一个数字类型的字符串进行测试,如"1343",执行结果如下:

python 复制代码
Entry with!
<__main__.ConvertType object at 0x1102ca450>
type:None
value:None
trace:None
Exit with!
Done!

说明在with...as语句块正常执行时,传入__exit__函数的参数type,value,trace都为None。

5,总结

发现python语言with语句有很大的魔力,类似我们编写try...except...finally结构的程序,但是with语句很优雅、便捷。在处理文件打开关闭、网络链接打开关闭、事务性程序的事务打开和关闭等都可以在__exit__函数中释放资源。

相关推荐
Full Stack Developme5 分钟前
java.nio 包详解
java·python·nio
零千叶21 分钟前
【面试】Java JVM 调优面试手册
java·开发语言·jvm
代码充电宝30 分钟前
LeetCode 算法题【简单】290. 单词规律
java·算法·leetcode·职场和发展·哈希表
li37149089035 分钟前
nginx报400bad request 请求头过大异常处理
java·运维·nginx
摇滚侠38 分钟前
Spring Boot 项目, idea 控制台日志设置彩色
java·spring boot·intellij-idea
新手村领路人44 分钟前
opencv gpu cuda python c++版本测试代码
python·opencv·cuda
高洁011 小时前
大模型-高效优化技术全景解析:微调 量化 剪枝 梯度裁剪与蒸馏 下
人工智能·python·深度学习·神经网络·知识图谱
white-persist1 小时前
CSRF 漏洞全解析:从原理到实战
网络·python·安全·web安全·网络安全·系统安全·csrf
Aevget1 小时前
「Java EE开发指南」用MyEclipse开发的EJB开发工具(二)
java·ide·java-ee·eclipse·myeclipse
游戏开发爱好者81 小时前
FTP 抓包分析实战,命令、被动主动模式要点、FTPS 与 SFTP 区别及真机取证流程
运维·服务器·网络·ios·小程序·uni-app·iphone