【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__函数中释放资源。

相关推荐
cdut_suye4 分钟前
Linux工具使用指南:从apt管理、gcc编译到makefile构建与gdb调试
java·linux·运维·服务器·c++·人工智能·python
qq_433618448 分钟前
shell 编程(三)
linux·运维·服务器
苹果醋316 分钟前
2020重新出发,MySql基础,MySql表数据操作
java·运维·spring boot·mysql·nginx
小蜗牛慢慢爬行17 分钟前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate
azhou的代码园20 分钟前
基于JAVA+SpringBoot+Vue的制造装备物联及生产管理ERP系统
java·spring boot·制造
dundunmm26 分钟前
机器学习之scikit-learn(简称 sklearn)
python·算法·机器学习·scikit-learn·sklearn·分类算法
古希腊掌管学习的神27 分钟前
[机器学习]sklearn入门指南(1)
人工智能·python·算法·机器学习·sklearn
一道微光40 分钟前
Mac的M2芯片运行lightgbm报错,其他python包可用,x86_x64架构运行
开发语言·python·macos
丘狸尾41 分钟前
[cisco 模拟器] ftp服务器配置
android·运维·服务器
黑客老陈1 小时前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
运维·服务器·前端·网络·安全·web3·xss