目录
with语句(一)
上下文管理器
with语句(二)
with语句(一)
我们都知道,平时在对文件里的数据进行进行读取或写入时都要先打开文件,完事之后再关闭文件。但有些客官记性不好(比如我),总是打开之后就忘了关闭。忘了关闭就会出事情,比如可能会造成文件数据丢失什么的,就会很危险!不过聪明的Python语言设计者早就预料到了有这种危险的可能,所以他们就发明了with语句。
以上纯属胡扯,下面正文开始
with 语句常用于对资源进行访问的场合,它能确保程序在对资源的使用之后执行必要的清理工作,释放资源(比如文件和数据库的自动关闭、线程中锁的自动获取和释放等),而不管在使用过程中是否发生异常。
那么对文件进行操作的代码就可以写成这样
with open('file.txt') as myfile:
myfile.write('Y')
利用这种写法,我们就不需要担心文件有没有关闭的问题了,因为 with 语句会自动帮我们做好这一切。但其实我们不是很明白,with 语句是怎么做到这一切的,难道它会魔法?
要想搞清楚上面 with 语句的魔法,我们就得先搞清楚with语句中的 open() ———一个上下文管理器
上下文管理器
先介绍俩术语
上下文管理协议:本协议指出,创建的任何类都必须至少定义两种魔法方法,即 __ enter__ 和 __ exit__ 方法。
上下文管理器:就是遵循上下文管理协议的类(class)。这个类定义了 __ enter__ 和 __ exit__ 方法。
再介绍俩魔法方法
__ enter__ 方法:该方法定义当使用 with 语句时的初始化行为,且 __ enter__ 的返回值(如果有的话)被 with 语句的
目标或者 as 后的名字绑定。
__ exit__ 方法:该方法定义 当一个代码组被执行或者终止后上下文管理器的行为。
哦,原来如此,open() 是一个类,而这个类中的 __ enter__ 和 __ exit__ 方法定义了 with 语句代码组开头和末尾的行为。那__ enter__ 和 __ exit__ 方法中肯定定义了打开文件和关闭文件行为,所以我们才不用手动打开和关闭文件了。啧啧,怪不得叫上下文管理器呢,原来是把 with 语句的开头(上文)和结尾(下文)都规定好了。
那我自己也能写一个上下文管理器喽,只要我定义__ enter__ 和 __ exit__ 方法
class MyContext():
def __enter__(self):
print('enter:呼叫with的代码组,呼叫with的代码组')
print('enter:这里是__enter__方法,我是你们的先遣部队,请代码组跟在我后头执行')
print('enter:over')
return '我是enter的返回值,with会用as给我起个别名,就是跟在as后面那个'
def __exit__(self, exc_type, exc_val, exc_tb):
print('exit:你们先上,我垫后')
with MyContext() as alias:
print(alias)
print('这里是with的代码组,我们正跟在先遣队之后')
'''
enter:呼叫with的代码组,呼叫with的代码组
enter:这里是__enter__方法,我是你们的先遣部队,请代码组跟在我后头执行
enter:over
我是enter的返回值,with会用as给我起个别名,就是跟在as后面那个
这里是with的代码组,我们正跟在先遣队之后
exit:你们先上,我垫后
'''
上面是写的一个上下文管理器,并用 with 语句对它进行了实验,结果很成功。
更常见的,当我们写一个上下文管理器时,除了两个必要的魔法方法,还会用的其他的方法,比如 __ init__ 方法(如果你想像open一样往管理器传入参数)等。我们常让 __ enter__ 方法做一些资源初始化的工作,让 __ exit__ 方法做一些清理和对异常的处理工作。
等一下,上下文管理器就是定义开头和结尾的行为,那么我定义一个只有一个 yield 的生成器函数(点进去了解生成器是什么),然后分别在代码的开头和结尾两次调用,岂不是也可以达到上下文管理器同样的效果?
没错,确实可以达到相同的效果。但是Python为了免除你两次调用的麻烦,在 contextlib 模块中提供了一个名为 contextmanager 的装饰器。当生成器函数被该装饰器装饰以后,会返回一个上下文管理器,该管理器的 __ enter__ 和 __ exit__ 方法由 contextmanager 负责提供。需要注意的是被装饰的生成器函数只能产生一个值,即只能有一个 yield 表达式,否则就会出现异常。产生的值会像 __ enter__ 的返回值一样,与 with 语句中 as 后面的变量发生绑定关系。例如下
from contextlib import contextmanager
# 导入装饰器
@contextmanager
def myContext():
print('这里是生成器myContext的前半部分')
print('我相当于上下文管理器中的__enter__方法')
print('eover')
yield '我相当于enter的返回值,with会用as给我起个别名,就是跟在as后面那个'
print('我相当于上下文管理器中的__exit__方法')
with myContext() as alias:
print(alias)
print('这里是with的代码组,我们正跟在先遣队之后')
'''
这里是生成器myContext的前半部分
我相当于上下文管理器中的__enter__方法
eover
我相当于enter的返回值,with会用as给我起个别名,就是跟在as后面那个
这里是with的代码组,我们正跟在先遣队之后
我相当于上下文管理器中的__exit__方法
'''
其实 contextlib 模块还提供了一个叫 nested 的函数和一个叫 closeing的上下文管理器
with语句(二)
我们通过下面那个一个类似 open() 的上下文管理器捋一遍 with 语句,再说说 __ exit__ 方法的那一大串参数都是什么东西。
class MyOpen():
def __init__(self, file_name, way='r'):
# 默认文件以只读的方式打开
self.file_name = file_name
self.way = way
def __enter__(self):
self.handle = open(self.file_name, self.way)
# 这里也可能产生异常,比如文件不存在或没有权限打开文件
# 所以也应该写一下产生异常的处理 用 try/except
return self.handle
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_val != None:
print('文件操作过程中产生了异常:',exc_type)
# 产生异常就输出 exc_type
self.handle.close()
with MyOpen('上下文管理.txt') as myfile:
data = myfile.readline()
print(data)
上面 with 语句的执行过程是:先执行上下文管理器的__ init__ 方法,然后执行 __ enter__ 方法,再然后执行 with 语句的代码组,最后执行上下文管理器的 __ exit__ 方法。
上下文管理器 MyOpen() 可以说是个阉割版的 open 吧,它实现了文件的打开和关闭操作,还有一个在 __ exit__ 方法中对异常的捕获和处理。
__ exit__ 方法可以自动捕获在它执行之前产生的异常,并把异常的类型,异常的值和异常回溯跟踪对象分别赋予它的后三个参数。其中 exc_type 是异常的类型, exc_val 是异常的值,exc_tb 是异常回溯跟踪对象。当该方法没有捕获异常时(之前没有产生异常),这三个参数都默认为 None 。所以可以根据这三个参数的值是否为空来判断有没有异常产生。