程序员

Python上下文管理器和with块

2019-08-14  本文已影响35人  董小贱

python中with语句用起来很方便,那这后边的原理就是python中的上下文管理器。

1.什么是上下文管理器

上下文管理器用来定义/控制代码块得执行前得准备工作,以及程序执行后得收尾工作。比如文件读写前的打开,与读写后的关闭。 网络请求的建立与断开等等。
下边拿requests.session() 为例:

import requests
with requests.session() as session:
    response_html = session.get('http://www.baidu.com').text
    print (response_html)

为什么with语句块能控制网络的建立与释放,原因是实现了上下文管理协议(context management protocol),相关的两个魔法方法:
__enter__(self): 不接收参数,定义上下文管理器在with语句块开头所执行的操作,其方法返回的值绑定到,/as 后边所定义的变量。
__exit__(self, exc_type, exc_value, traceback):接收三个参数,exc_type 表示错误类型;exc_value 异常实例。有时会有参数传给异常构造方法,例如错误消息,这些参数可以使用exception_type获取。traceback:traceback对象

session的上下文协议源码


session源码

2.创建上下文管理器的两种方式

我们重新实现一个类似open()的类,只是在创建文件对象时和关闭文件对象时增加了提示。

2.1 实现上下文协议
class File(object):
    def __init__(self, file_path, mode):
        self.file_path = file_path
         self.mode = mode

    def __enter__(self):
        self.f = open(self.file_path, self.mode, encoding='utf-8')
        print(f"{self.file_path} is open")
        return self.f

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()
        print(f"{self.file_path} is closed")


with File('test.txt', 'w') as f:
    f.write('董小贱')

输出结果:

test.txt is open
test.txt is closed
2.2 装饰器实现
from contextlib import contextmanager

@contextmanager
def file(file_path, mode):
    f = open(file_path, mode, encoding='utf-8')
    print(f"{file_path} is open")

    yield f
    f.close()
    print(f"{file_path} is closed")

with file('test.txt, 'w') as f:
    f.write('董小贱')

输出结果:

test.txt is open
test.txt is closed

在使用 @contextmanager 装饰的生成器中,yield 语句的作用是把函数的定义体分成两部 分:yield 语句前面的所有代码在 with 块开始时(即解释器调用 __enter__方法时)执行, yield 语句后面的代码在 with 块结束时(即调用 __exit__ 方法时)执行。

其实这里有个问题,如果在with块中抛出了一个异常,Python 解释器会将其捕获, 然后在 file 函数的 yield 表达式里再次抛出,但是代码中没有处理异常的代码,所以函数会终止,导致文件不能被关闭。所以我们要在yield处处理异常。

所以第二版:

from contextlib import contextmanager

@contextmanager
def file(file_path, mode):
    f = open(file_path, mode, encoding='utf-8')
    print(f"{file_path} is open")
    try:
        yield f
    except Exception as e:
        print (e) # 处理异常的操作
    finally:
        f.close()
        print(f"{file_path} is closed")
with file('24.txt', 'w') as f:
    f.write('董小贱')

注:实现上下文管理协议,即可使用with块。需要注意的是在 @contextmanager 装饰器装饰的生成器中,yield 与迭代没有任何关系。

上下文管理器一个很好的例子 原地文件读写管理器

更多细节,请参考 《流畅的python》的第十五章节。

上一篇下一篇

猜你喜欢

热点阅读