详细理解并举例分析Python中的上下文管理器
理解Python的上下文管理器的原理和使用
一 背景
很久前看了《流畅的Python》一书,未做笔记,最近学FastAPI框架时,学到了在FastAPI中使用到了上下文管理器,特此来把笔记补上,加深学习印象!
二 简介
with作为python上下文管理的关键字,它会设置一个临时的上下文,交给上下文的管理器对象控制,并且负责清理上下文,避免错误以及减少重用代码。
用一句话简要概括: 上下文管理器的存在母的是管理with语句,就像迭代器的存在是为了管理for语句
三 探索with
1.with的语句是简化重复的try/finally在语句中多次出现,并完成try/finally该做的事情。说到这,我觉得装饰器和上下文管理器有着异曲同工之妙,面向切面编程。
2.上下文管理器的协议包含__enter__
和__exit__
方法。
3.以with open() as f:
为例子大致执行流程:
(1)首先with开始运行时,会在上下文管理器中调用__enter__
方法,将__enter__
返回的值绑定到f
对象上,这样就得到了上下文管理对象,接下来在with代码体内都可以通过f来调用相关属性(例如这里的f就是文件对象,那么我就可以通过f来调用文件对象中的属性和方法)。退出with代码块时调用__exit__
。
(2)__enter__
返回的也不一定是上下文对象(自身),也可以是其他对象
(3)注意要绑定对象,必须使用as
。
举一个最简单的例子:
1 |
|
执行流程:先进入with前,执行__enter__
,然后调用compute
,最后退出with时调用__exit__
。
注意点:
1.从上述__exit__
代码的参数中可以看出需要传入三个参数,分别为异常类型,异常实例(通过exc_value.args来获取异常值),回朔点对象。
2.注意看__exit__
中最后一句是return True
,这里会告诉解释器异常已经处理,解释器会压制异常,从而不会报错。而如果不写return True
时,则解释器仍会抛出异常。这一点会在@contextmanager装饰器中再次提到
四 常用的contextlib.@contextmanager装饰器
1.在抽象AbstractContextManager(出现在3.6中)定义了该两个函数的默认抽象方法,同时定义了一个钩子函数,用于检查自定义的上下文管理器类是否具备__enter__
,和__exit__
方法。(也就是继承了抽象类,必须要存在这两个方法)
1 |
|
(1)默认的__enter__
返回的是上下文管理器对象自身。
(2)默认的__exit__
返回的是None
2.AbstractAsyncContextManager和AbstractContextManager的区别主要在前者之处异步,后者支持同步。
3.@contextmanager(出现在3.7版本)是一个装饰器,装饰器在这里的作用是简化代码,通过装饰器来装饰函数,而不需要定义一个类,包含两个必要的函数(__enter__
,__exit__
)。这样在函数中需用yield来作为__enter__
和__exit__
的临界点。yield的前半段的代码作用等同于调用了__enter__
,后半段等同与调用了__exit__
。
4.@contextmanager会将函数包装成实现了__enter__
和__exit__
。
5.会在退出with的作用域后调用__exit__
或执行yield之后的代码段
仍用上面的1/0举个例子:
1 |
|
注意:
使用调试模式运行,在with下面的所有语句(包括自身)打上断点,就可以直观的感受到是如何运行的了,@contextlib.contextmanager是语法糖,能够在无需__enter__
和__exit__
方法,通过单yield实现
上下文管理器功能。
4.1 @contextmanager中等价于__enter__
的功能作用:
(1)调用生成器函数(被装饰的函数),保存生成器对象obj。
(2)调用next(obj),执行到yield关键字所在的位置处。
(3)将yield value产出的值,绑定到as 后指明的目标变量上。
4.2 @contextmanager中等价于__exit__
的功能作用:
(1)检查有没有把异常传给exc_type,如果有则调用obj.throw,抛出异常。
(2)在生成器函数yield关键字那一行抛出抛出异常(可以回滚到yield),在yield后半段处理异常
1 |
|
(3)否则没有异常,则继续调用next(obj),执行yield语句以后的代码。
注意点:
刚才上面提到了自定义的上下文类中的__exit__
方法中使用了return True
来压制异常。但是在@contextmanager
中由于没有显示的__exit__
方法存在,所以其装饰器会默认异常已经被处理,也就是强制压制了异常,因此在使用@contextmanager
的时候必须要在yield处用try和catch捕获异常。然后捕捉到在yield处抛出。
五 FastApi中所提到的Dependencies with yield
1.如果对FastApi不感兴趣的可以不用往下看了。
2.FastApi中使用到了一种解耦+复用的机制—-依赖注入,依赖注入在Spring中也被广泛使用到。而FastApi将依赖注入和上下文管理器结合到了一起。
举个官网的例子
1 |
|
分析:
1.FastApi中对上下文管理器的用法和普通的上下文管理器有一些差别,但基本原理是一致的。
2.FastApi中设定了这样一种机制,它允许在__exit__
中依赖项处理异常程序之外自定义异常处理程序。其中依赖项退出代码相当于在__exit__
中定义的函数。而自定义异常需要自定义异常处理程序处理,不能由依赖项退出代码处理(__exit__
\yield之后)
3.FastApi在捕获处理了HttpExcpetion后,其他自定义的异常处理程序将不能再捕获该异常。
4.如果在后台运行期间(路径函数中)发生了异常,可以会滚到yield处进行捕获抛出。
5.进入到yield后的代码段,将执行异常处理程序,同时将不能再对response做出修改(此时已经退出了路径函数作用域)。
6.总的来说,其实并不需要在fastapi中使用@contextmanager装饰器或者自定义上下文管理器类实现,因为它已经将其和依赖注入结合封装好了(Depends),要学习它的原理。
结合官方的一张图能更好的理解:
{width=100%}
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!