吃透Python上下文管理器
什么是上下文管理器?
我们常见的with open
操作文件,就是一个上下文管理器。如:
with open(file, 'rb') as f:
text = f.read()
那上下文管理器具体的定义是什么呢?
上下文管理器
:是指在一段代码执行之前执行一段代码,用于一些预处理工作;执行之后再执行一段代码,用于一些清理工作。
比如刚提到的文件操作,打开文件进行读写,读写完之后需要将文件关闭。很明显用到了上下文管理器。主要依靠__enter__
、__exit__
这两个”魔术方法”实现。
__enter__(self)
Defines what the context manager should do at the beginning of the block created by the with statement. Note that the return value of enter is bound to the target of the with statement, or the name after the as.
__exit__(self, exception_type, exception_value, traceback)
Defines what the context manager should do after its block has been executed (or terminates). It can be used to handle exceptions, perform cleanup, or do something always done immediately after the action in the block. If the block executes successfully, exception_type, exceptionvalue, and traceback will be None. Otherwise, you can choose to handle the exception or let the user handle it; if you want to handle it, make sure exit_ returns True after all is said and done. If you don’t want the exception to be handled by the context manager, just let it happen.
当我们需要创建一个上下文管理器类型的时候,就需要实现__enter__
和__exit__
方法,这对方法称为上下文管理协议(Context Manager Protocol),定义了一种运行时上下文环境。
基本语法
with EXPR as VAR:
BLOCK
这里就是一个标准的上下文管理器的使用逻辑,其中的运行逻辑:
(1)执行EXPR语句,获取上下文管理器(Context Manager)
(2)调用上下文管理器中的__enter__方法,该方法执行一些预处理工作。
(3)这里的as VAR可以省略,如果不省略,则将__enter__方法的返回值赋值给VAR。
(4)执行代码块BLOCK,这里的VAR可以当做普通变量使用。
(5)最后调用上下文管理器中的的__exit__方法。
(6)__exit__方法有三个参数:exc_type, exc_val, exc_tb。如果代码块BLOCK发生异常并退出,那么分别对应异常的type、value 和 traceback。否则三个参数全为None。
(7)__exit__方法的返回值可以为True或者False。如果为True,那么表示异常被忽视,相当于进行了try-except操作;如果为False,则该异常会被重新raise。
如何自己实现上下文管理器?
简单来说,如果一个类中,实现了__enter__
和__exit__
方法,那么这个类就是上下文管理器。
class Contextor():
def __enter__(self):
print('程序的预处理开始啦!')
return self # 作为as说明符指定的变量的值
def __exit__(self, exc_type, exc_val, exc_tb):
print('正在进行收尾!')
def func(self):
print('程序进行中....')
with Contextor() as var:
var.func()
# 输出
程序的预处理开始啦!
程序进行中....
正在进行收尾!
从这个示例可以很明显的看出,在编写代码时,可以将资源的连接或者获取放在__enter__
中,而将资源的关闭写在__exit__
中。
为什么要使用上下文管理器?
在我看来,这和 Python 崇尚的优雅风格有关。
- 可以以一种更加优雅的方式,操作(创建/获取/释放)资源,如文件操作、数据库连接;
- 可以以一种更加优雅的方式,处理异常;
第二种,会被大多数人所忽略。这里着重介绍下。
在处理异常时,通常都是使用try...except...
来进行异常处理的。这就可能会出现在程序的主逻辑中有大量的异常处理代码,这会大大影响程序的可读性。
好的做法可以通过with
将异常处理隐藏起来。
我们以1/0
举例(1/0
必然会抛出错误)
class Resource():
def __enter__(self):
print('===connect to resource===')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('===close resource connection===')
return True
def operate(self):
1/0
with Resource() as res:
res.operate()
# 输出结果
===connect to resource===
===close resource connection===
运行发现,并没有出异常。
这就是上下文管理器的强大之处,异常可以在__exit__
进行捕获,并自己决定如何处理。在__exit__
里返回 True
(没有return 就默认为 return False),就相当于告诉 Python解释器,这个异常我们已经捕获了,不需要再往外抛了。
在 写__exit__
函数时,需要注意的事,它必须要有这三个参数:
- exc_type:异常类型
- exc_val:异常值
- exc_tb:异常的错误栈信息
当主逻辑代码没有报异常时,这三个参数将都为None。
如何处理自行处理异常
我们以上面的代码为例,在__exit__
加入判断异常的逻辑,如果发生异常,则打印异常信息。
class Resource():
def __enter__(self):
print('===connect to resource===')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
print(f'出现异常:{exc_type}:{exc_val}')
print('===close resource connection===')
return True
def operate(self):
1/0
with Resource() as res:
res.operate()
# 输出
===connect to resource===
出现异常:<class 'ZeroDivisionError'>:division by zero
===close resource connection===
如何更好的使用上下文管理器?
在Python中有一个专门用于实现上下文管理的标准库contextlib
。
有了 contextlib
创建上下文管理的最好方式就是使用 contextmanager 装饰器,通过 contextmanager 装饰一个生成器函数,yield
语句前面的部分被认为是__enter__()
方法的代码,后面的部分被认为是 __exit__()
方法的代码。
我们以打开文件为例:
import contextlib
@contextlib.contextmanager
def open_func(file_name):
# __enter__方法
print('open file:', file_name, 'in __enter__')
file_handler = open(file_name, 'r')
# 【重点】:yield
yield file_handler
# __exit__方法
print('close file:', file_name, 'in __exit__')
file_handler.close()
return
with open_func('/Users/MING/mytest.txt') as file_in:
for line in file_in:
print(line)
如果要处理异常,将上面代码改写成下面的样子。
import contextlib
@contextlib.contextmanager
def open_func(file_name):
# __enter__方法
print('open file:', file_name, 'in __enter__')
file_handler = open(file_name, 'r')
try:
yield file_handler
except Exception as exc:
# deal with exception
print('the exception was thrown')
finally:
print('close file:', file_name, 'in __exit__')
file_handler.close()
return
with open_func('test.txt') as file_in:
for line in file_in:
1/0
print(line)
参考:
https://juejin.im/post/5c87b165f265da2dac4589cc
https://www.cnblogs.com/linxiyue/p/3855751.html
https://runnerliu.github.io/2018/01/02/pythoncontextmanager/
扫描下面二维码,关注公众号, 每周定期与您分享原创的、有深度的Python知识点。
吃透Python上下文管理器的更多相关文章
- python 上下文管理器
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 上下文管理器(context manager)是Python2.5开始支持的一种语 ...
- Python 上下文管理器和else块
最终,上下文管理器可能几乎与子程序(subroutine)本身一样重要.目前,我们只了解了上下文管理器的皮毛--Basic 语言有with 语句,而且很多语言都有.但是,在各种语言中 with 语句的 ...
- Python上下文管理器
在Python中让自己创建的函数.类.对象支持with语句,就实现了上线文管理协议.我们经常使用with open(file, "a+") as f:这样的语句,无需手动调用f.c ...
- python上下文管理器ContextLib及with语句
http://blog.csdn.net/pipisorry/article/details/50444736 with语句 with语句是从 Python 2.5 开始引入的一种与异常处理相关的功能 ...
- Python上下文管理器 with
对于系统资源的操作,如:文件操作.数据库操作等,我们往往打开文件.连接数据库后忘了将其close掉,这时就可能会引发异常,因此我们常用的做法是: # coding:utf-8 f = open(&qu ...
- Python上下文管理器(Context managers)
上下文管理器(Context managers) 上下文管理器允许你在有需要的时候,精确地分配和释放资源. 使用上下文管理器最广泛的案例就是with语句了.想象下你有两个需要结对执行的相关操作,然后还 ...
- python上下文管理器细读
test 1 上下文管理器,将生成器转化为上下文管理器 import contextlib @contextlib.contextmanager def a(): print(1) yield pri ...
- python 上下文管理器contextlib.ContextManager
1 模块简介 在数年前,Python 2.5 加入了一个非常特殊的关键字,就是with.with语句允许开发者创建上下文管理器.什么是上下文管理器?上下文管理器就是允许你可以自动地开始和结束一些事情. ...
- Python 上下文管理器模块--contextlib
在 Python 处理文件的时候我们使用 with 关键词来进行文件的资源关闭,但是并不是只有文件操作才能使用 with 语句.今天就让我们一起学习 Python 中的上下文管理 contextlib ...
随机推荐
- wordpress开源小程序
wordpress多端开源小程序正式发布了,目前支持微信/QQ/百度/今日头条. 目前我们的开源小程序,已经建立了微信QQ交流群,需要的可以加下,微信添加hackdex(备注开源拉你入群),QQ群:7 ...
- 像黑客一样写博客–Pelican快速搭建静态博客
"像黑客一样写博客",通过文本编辑器(Markdown编辑器)即可实现写博客,而且是静态的,很神奇吧,这里的方案是Pelican. 为啥叫 Pelican 这么奇怪的名字 &quo ...
- 【集训Day1 测试】【USACO】照相
照相(fairphoto) [题目描述] 有N 头奶牛站在一条数轴上,第 i 头奶牛的位置是 Pi,奶牛不会重叠站在同一个位置, 第i 头奶牛的颜色是 Ci,其中 Ci 要么是字符'G'要么是字符'H ...
- day 15 内置函数二 递归 lamda sorted filter map 二分法求值
回顾 for i in dict #对字典进行遍历,拿到的是字典的key 今日主要内容 1. lambda 匿名函数 语法: lambda 参数:返回值 不能完成复杂的操作.只能写一行 注意: 1 ...
- Dockerfile构建私有镜像
构建第一个镜像 镜像的定制实际上就是定制每一层所添加的配置,文件.我们可以把每一层修改,安装,构建,操作的命令都写入一个脚本,这个脚本就是Dockerfile.Dockerfile是一个文本文件,其内 ...
- 网络层 IP
网络层 -- 数据包 网络层作用 解决什么问题? 在讲网络层之前,其实基于广播的这种通信就可以实现全世界通信了,你吼一声,如果全世界是一个局域网,全世界的计算机肯定可以听得见,从理论上似乎行得通,如果 ...
- c语言I博客专业04
问题 答案 这个作业属于那个课程 C语言程序设计II 这个作业要求在哪里 https://edu.cnblogs.com/campus/zswxy/CST2019-2/homework/8655 我在 ...
- .Neter所应该彻底了解的委托
本文将通过引出几个问题来,并且通过例子来剖析C#中的委托以及用法,做抛砖引玉的作用 对于委托我发现大部分人都有以下问题,或者可能在面试中遇过这样的: 委托是不是相当于C/C++的函数指针? 委托究竟是 ...
- 以面向对象的思维,搭建Android与多ble蓝牙设备并发通讯小框架
Android连接多蓝牙设备.蓝牙与多设备连接.蓝牙ble多设备并发操作.Android连接不了.Android ble开发框架.Android 连接蓝牙总结 前言 小白请绕道百度,本文适合有一定An ...
- mysql 授权用户 主从和备份
1.授权用户 mysql -uroot -p123qqq...A 进入数据库 grant all on *.* to dc@&q ...