之前只知道with...as语句可以作用于一些资源文件的操作,比如filelike对象,可以使它打开后,即使异常也自动关闭,还有线程锁的获取与释放。
其实with的作用对象不止这些,只要是上下文管理器对象,都能应用with语句
什么是上下文管理器?
我们用with打开一个文件对象,with语句其实实现了 打开文件,执行代码块,关闭文件的流程。上下文管理器其实就是 在执行代码块 之前做一些准备,做完之后在做一些善后的工作,这就完成了对代码块中的这一部分的上下文的管理。
怎么实现上下文管理器?
- 自定义类实现两个魔法方法:enter,exit
class Son(object):
def __init__(self,name):
self.name = name
def __enter__(self):
print('%s诞生了'%self.name)
def __exit__(self, exc_type, exc_val, exc_tb):
print('顺利诞生')
s = Son('wu')
with s as g:
print(s.name)
结果:
wu诞生了
wu
顺利诞生
enter方法中执行的工作就是进入代码块前干的准备工作,他的返回值可以用as接收,而exit中干的就是代码块执行完后的工作。
若在执行代码块中发生异常,exit方法仍会执行,并且可以捕获到异常信息,具体详情我还没搞明白。
2.通过@contextmanager将一个生成器变为上下文管理器
@contextmanager是contextlib模块下的一个装饰器,他接收的函数必须是生成器
import contextlib
@contextlib.contextmanager
def get_file(filename):
print('正在打开文件')
f = open(filename,'r',encoding='utf8') #代码块执行前的准备工作
yield f
print('正在关闭文件')
f.close()#执行后的善后工作
with get_file('test.py') as g:
print('正在读取文件')
print(g.read())
结果:
正在打开文件
正在读取文件
#coding=utf8
文件内容
正在关闭文件
在被装饰的生成器函数中,yield之前的部分就是 代码块执行前准备的内容,yield之后的就是代码块执行后的善后处理,yield返回的值可以用 as 赋予一个变量,在代码块中使用。
通过contextmanager装饰的生成器返回的是上下文管理器,这样就可以用with语句了。
3.通过context.closing类把一个对象变为上下文管理器对象
import contextlib
from urllib.request import urlopen
with contextlib.closing(urlopen('http://wwww.baidu.com')) as f:
print(f.readline())
这是closing类的源码,其实就是传入对象初始化一个上下文管理器类对像,在exit中实现调用close(),在enter中返回原来的对象,用as可以获取
class closing(AbstractContextManager):
def __init__(self, thing):
self.thing = thing
def __enter__(self):
return self.thing
def __exit__(self, *exc_info):
self.thing.close()
总结一下:上下文管理器总是配合with语句一起使用,作用是在with语句块的执行前和执行后完成指定功能。
创建上下文管理器的方法有三种:
- 自定义类,实现enter,和exit方法
- 通过 @contextmanager将一个生成器变为上下文管理器,yield前后分别在代码块前后执行
- 利用context.closing类把一个对象变为上下文管理器对象,传入对象参数,在exit中调用对象的close方法