Python核心技术与实战——二一|巧用上下文管理器和with语句精简代码
我们在Python中对于with的语句应该是不陌生的,特别是在文件的输入输出操作中,那在具体的使用过程中,是有什么引伸的含义呢?与之密切相关的上下文管理器(context manager)又是什么呢?
什么是上下文管理器
在任何一种编程语言里,文件的输入输出、数据库的建立连接和断开等操作,都是很常见的资源管理操作。但是资源是有限的,在写程序的时候,我们必须保证这些资源在使用后得到释放,不然就容易造成资源泄漏,轻者系统处理缓慢,重则系统崩溃。
我们看一个例子:
for i in range(100000000):
f = open('test.txt','w')
f.write('hello')
我们在循环里打开了100000000个文件,但是在使用完毕后没有进行关闭操作,一运行代码,就报错了。
这就是一个典型的资源泄漏的案例,因为程序中同时打开了太多的文件,占用了太多的资源,造成崩溃。
为了解决这个问题,不同的编程语言都引入了不同的机制,在Python中,对应的解决方法就是上下文管理器(context manager)。上下文管理器能够自动分配资源并释放资源,其中最典型的应用就是with语句,所以上面的代码应该用这种方式来写
for x in range(100000000):
with open('test.txt','w') as f:
f.write('hello world')
这样,我们每次打开文件“test.txt"并写入字符以后,这个文件就会自动关闭,相应的资源也就得以释放,可以防止资源泄漏。当然,with的语句也可以用下面的方式来表示
f = open('test.txt','w')
try:
f.write('hello world')
finally:
f.close()
这里一定要注意finally的程序段,哪怕在写入的时候发生了异常,他也可以保证文件最终被关闭。不过于with想比较,就显得比较冗余了,并且还容易忽略finally,所以我们平时更倾向于使用with语句。
另外一种很典型的例子,就是Python中的线程锁(threrading.lock类),比如我们想要获得一个锁,执行相应的操作以后再将其释放,那么代码就应该是这样的
import threading
some_lock = threading.Lock()
some_lock.acquire()
try:
pass
finally:
some_lock.release()
而与其对应的with语句就非常简洁了
import threading
some_lock = threading.Lock()
with some_lock:
pass
从上面两个例子可以发现,使用with语句,可以大大的简化代码结构,有效的避免资源泄漏的发生。
上下文管理器的实现
基于类的上下文管理器
了接了上下文管理的概念和优点以后,我们就通过下面的例子,看看上下文管理器的原理,高清他的内部实现。我们在这里定义一个上下文管理类FileManager,来模拟Python的打开、关闭文件的操作
class FileManager():
def __init__(self,name,mode):
print('call __init__ method')
self.name = name
self.mode = mode
self.file = None def __enter__(self):
print('calling __enter__ method')
self.file = open(self.name,self.mode)
return self.file
def __exit__(self,exc_type,exc_val,exc_tb):
print('call __exit__ method')
if self.file:
self.file.close() with FileManager('test.txt','w') as f:
print('ready to write to file')
f.write('hello world') ##########输出##########
call __init__ method
calling __enter__ method
ready to write to file
call __exit__ method
特别注意:当我们用类来创建上下文管理器的时候,必须保证这个类包括下面两个方法:
__enter__()
__exit__()
并且enter方法还要返回需要被管理的资源,方法exit里通常会存在一些释放、清理资源的操作,比如上面这段代码里的关闭文件等。
而当我们用with语句来执行上面这个上下文管理器的时候,会发生下面四个步骤:
1.构造方法__init__()会被调用,程序初始化对象FileManager,使得文件名和操作方式被传入。
2.方法__enter__()被调用,文件被以写入的模式打开,并且返回FileManager对戏那个赋值给变量f
3.字符串被写入文件
4.方法__exit__()被调用,关闭之前打开的文件流。
所以就有了上面列出的输出结果。
另外我们可以看到exit函数里传递了几个参数——exc_type.exc_val,exc_tb,分别表示exception_type,exception_value和traceback。当我们执行含有上下文管理器的with语句的时候,如果有异常抛出,异常的信息就会被包含在上面三个参数中,传给__exit__()函数。
因此,如果我们需要处理一些异常,可以在__exit__()函数中添加相应的代码
def __exit__(self,exc_type,exc_val,exc_tb):
print('call __exit__ method')
if exc_type:
print(f'exc_type:{exc_type}')
print(f'exc_value:{exc_val}')
print(f'exc_traceback:{exc_tb}')
print('exception handled')
return True
if self.file:
self.file.close() with FileManager('test.txt','w') as f:
raise Exception('exception raised').with_traceback(None)
在修改了exit()方法以后我们在with语句中用raise手动抛出异常,我们可以看到代码有下面的输出
call __init__ method
calling __enter__ method
call __exit__ method
exc_type:<class 'Exception'>
exc_value:exception raised
exc_traceback:<traceback object at 0x000001AA82C8FF48>
exception handled
要注意的是,如果exit函数如果没有返回True,呢么异常仍然会被抛出。如果我们确定了异常已经被处理,那么在exit最后要加上True的返回值。
同样的,在数据库的连接等操作上,也常常使用上下文管理器来表示,下面是个简化的代码
class DBConnectionManager():
def __init__(self,hostname,port):
self.hostname = hostname
self.port = port
self.connection = None def __enter__(self):
self.connection = DBClient(self.hostname,self.port)
return self def __exit__(self,exc_type,exc_val,exc_tb):
self.connection.close() with DBConnectionManager('localhost','') as db_client:
pass
代码的具体含义和上面的例子类似,就不再详细说明了。只要我们写完了DBConnectionManager这个类以后,在每次建立数据库连接时,只要简单的利用with语句就可以了,并不要关系数据库的关闭、异常等,大大的提高了开发效率。
基于生成器的上下文管理器
上面那种基于类的上下文管理器在Python中利用非常广泛,我们在很多项目中都可以看得到,不过Python中的上下文管理器不仅仅局限于此,畜类基于类,它还可以基于生成器实现,我们看看下面的例子:
我们用一个装饰器contextlib.contextmanager来定义自己所需要的基于生成器的上下文管理器,用以支持with语句。同样我们用前面的FileManager来演示
from contextlib import contextmanager @contextmanager
def file_manager(name,mode):
try:
f = open(name,mode)
yield f
finally:
f.close() with file_manager('test.txt','w') as f:
f.write('hello world')
这段代码中,函数file_manager()是一个生成器,当我们执行with语句的时候,便会打开文件,并返回文件对象f,当with语句执行完毕以后,finally代码段中的关闭操作就会执行。
可以看到,使用基于生成器的上下文管理器的时候,我们不用再定义__enter__()和__exit__()两个函数,但是必须加上装饰器,这一点非常容易漏掉。
讲完这两种上下文管理器以后,我们要强调一点:不论是基于类的还是生成器的上下文管理器,两者在功能上是一样的,只不过有下面两点:
1.基于类的上下文管理器更加灵活,适用于大型的系统开发
2.基于生成器的上下文管理器更加方便、简洁,适用于中小型程序。
但是无论使用哪一种,我们一定要记得在exit函数或finally里写好释放资源的代码,这一点尤为重要。
总结
在这一章的开头我们通过一个简单的例子了解了资源泄漏的易发生的特性和其带来的后果,从而引入了上下文管理器这个概念:
上下文管理器通常用在文件的IO操作和数据库的连接关闭等场景中,可以确保用过的资源得到迅速释放,有效提高了程序的安全性。
接着,我们通过自定义上下文管理器的示例,大致了解了上下文管理器工作的原理,并介绍基于类的上下文管理器和基于生成器的上下文管理器:两者功能相同,具体使用哪个要根据场景来选择。
另外,上下文管理器通常和with一起使用,大大提高了程序的简洁度,需要注意的是我们在使用with语句执行上下文操作的时候,一旦有异常抛出,异常的类型、值等拘役信息都会通过参数传递给__exit__()函数,我们可以自行定义相关的操作,而在对异常处理完毕以后,务必加上return True语句来保证程序的执行,否则仍然会抛出异常。
Python核心技术与实战——二一|巧用上下文管理器和with语句精简代码的更多相关文章
- 翻译《Writing Idiomatic Python》(五):类、上下文管理器、生成器
原书参考:http://www.jeffknupp.com/blog/2012/10/04/writing-idiomatic-python/ 上一篇:翻译<Writing Idiomatic ...
- Python中的上下文管理器和with语句
Python2.5之后引入了上下文管理器(context manager),算是Python的黑魔法之一,它用于规定某个对象的使用范围.本文是针对于该功能的思考总结. 为什么需要上下文管理器? 首先, ...
- Python高级笔记(八)with、上下文管理器
1. 上下文管理器 __enter__()方法返回资源对象,__exit__()方法处理一些清除资源 如:系统资源:文件.数据库链接.Socket等这些资源执行完业务逻辑之后,必须要关闭资源 #!/u ...
- Python核心技术与实战——二十|assert的合理利用
我们平时在看代码的时候,或多或少会看到过assert的存在,并且在有些code review也可以通过增加assert来使代码更加健壮.但是即便如此,assert还是很容易被人忽略,可是这个很不起眼的 ...
- Python核心技术与实战——二十|Python的垃圾回收机制
今天要讲的是Python的垃圾回收机制 众所周知,我们现在的计算机都是图灵架构.图灵架构的本质,就是一条无限长的纸带,对应着我们的存储器.随着寄存器.异失性存储器(内存)和永久性存储器(硬盘)的出现, ...
- python上下文管理器及with语句
with语句支持在一个叫上下文管理器的对象的控制下执行一系列语句,语法大概如下: with context as var: statements 其中的context必须是个上下文管理器,它实现了两个 ...
- Python进阶(上下文管理器与with语句)
/*上下文管理器必须有__enter__和__exit__方法*/ class MyResource: def __enter__(self): print('链接资源') return self / ...
- Python核心技术与实战 笔记
基础篇 Jupyter Notebook 优点 整合所有的资源 交互性编程体验 零成本重现结果 实践站点 Jupyter 官方 Google Research 提供的 Colab 环境 安装 运行 列 ...
- 【Python学习笔记】with语句与上下文管理器
with语句 上下文管理器 contextlib模块 参考引用 with语句 with语句时在Python2.6中出现的新语句.在Python2.6以前,要正确的处理涉及到异常的资源管理时,需要使用t ...
随机推荐
- java:dubbo
demo_dubbo_consumer Maven Webapp: DubboController.java: package com.dubbo.controller; import java.ut ...
- windows下sqlplus怎么连接远程oracle
语法:sqlplus usr/pwd@//host:port/sid [oracle@mzl ~]$ sqlplus system/51411482@//192.168.21.11:1521/orcl ...
- DOM事件练习 I
目录 input框动态显示事件 红绿灯模拟 顶部广告栏关闭 鼠标悬停IMG上时,更换另一张图片 悬浮框自动出现 模态框案例 input框动态显示事件 <head> <meta cha ...
- 安卓的一些UI美化框架的使用
目录 一.前言 二.Android-Bootstrap 三.Sweet Alert Dialog 四.ExplosionField 一.前言 在这里记录一些用到过的觉得还算不错的UI第三方开源美化框架 ...
- 分布式锁用Redis还是ZooKeeper?(转载)
文章系网络转载,侵删. 来源:https://zhuanlan.zhihu.com/p/73807097 为什么用分布式锁?在讨论这个问题之前,我们先来看一个业务场景. 图片来自 Pexels 为什么 ...
- 关于绕过cookie 同源策略,和同时向前台返回图片和脚本的解决方案
绕过cookie的同源策略 向前端写入脚本时使用domain来绕过同源策略. 比如 domain= baidu.com .次脚本生成的cookie可以在 *.baidu.com中使用 /// < ...
- Linux学习笔记(16)Linux前后台进程切换(fg/bg/jobs/ctrl+z)
关键词:Linux前后台进程切换,linux进程切换 fg.bg.jobs.&.ctrl + z都是跟系统任务有关的,虽然现在基本上不怎么需要用到这些命令,但学会了也是很实用的一.& ...
- kafka安装使用配置1.1
官方文档 rz上传到/usr/local/下 解压 tar xzvf 文件 改名 mv 文件 名字 环境变量 vi /etc/profile export KAFKA_HOME=/usr/local/ ...
- .Net Core 中使用NLog作为日志中间件
⒈安装相关依赖 NLog NLog.Web.AspNetCore ⒉在项目的根目录中创建NLog配置文件 <?xml version="1.0" encoding=" ...
- MateBook 换内存条
欢迎关注微信公众号:猫的尾巴有墨水 为啥要拆MateBook D笔记本? 最近这个Windows 10更新后,内存暴增,每次禁用windows update和同步服务模块后,依然不能彻底解决内存爆炸的 ...