【Python】with 上下文管理器

150 阅读2分钟

with 的语法格式:

 with context_expression [as target(s)]:
     pass

上下文管理器协议

使用 with 语法块,with 后面的的对象需要实现「上下文管理器协议」

在 Python 中,一个类实现以下两个魔法方法,就实现了「上下文管理器协议」:

  • __enter__:在进入 with 语法块之前调用,返回值会赋值给 withtarget
  • __exit__:在退出 with 语法块时调用,一般用作异常处理
 class Test:
 ​
     def __enter__(self):
         print('function: __enter__')
         return 1
 ​
     def __exit__(self, exc_type, exc_val, exc_tb):
         print(f'exc_type: {exc_type}')
         print(f'exc_val: {exc_val}')
         print(f'exc_tb: {exc_tb}')
 ​
 with Test() as t:
     print(f't: {t}')
 ​
 """
 function: __enter__
 t: 1
 exc_type: None
 exc_val: None
 exc_tb: None
 """
 ​
 with Test() as t:
     print(f't: {t}')
     a = 1/0
 """
 t: 1
 exc_type: <class 'ZeroDivisionError'>
 exc_val: division by zero
 exc_tb: <traceback object at 0x0000021F57012B40>
 """

说明:__enter__方法在进入with后的函数块之前被调用,并将该函数返回值赋值给t,然后执行函数块中内容,执行过程中若发生异常信息,则交给__exit__方法处理

  • exc_type:异常类型
  • exc_value:异常对象
  • exc_tb:异常堆栈信息

此外,之所以 with 能够自动关闭文件资源,就是因为内置的文件对象实现了「上下文管理器协议」,这个文件对象的 __enter__ 方法返回了文件句柄,并且在 __exit__ 中实现了文件资源的关闭,另外,当 with 语法块内有异常发生时,会抛出异常给调用者。

contextlib 模块

Python 标准库提供的 contextlib 模块,可以把上下文管理器当成一个「装饰器」来使用,该模块提供了 contextmanager 装饰器和 closing 方法。

contextmanager 装饰器的使用

使用 contextmanager 装饰器和 yield配合,实现和前面上下文管理器相同的功能

 from contextlib import contextmanager
 ​
 @contextmanager
 def test():
     print('--before--')
     yield 1
     print('--after--')
 ​
 with test() as t:
     print(t)
 """
 --before--
 1
 --after--
 """

说明:

  1. 执行 test() 方法,先打印出 before
  2. 执行 yield 'hello'test 方法返回,hello 返回值会赋值给 with 语句块的 t 变量
  3. 执行 with 语句块内的逻辑,打印出 t 的值 hello
  4. 又回到 test 方法中,执行 yield 后面的逻辑,打印出 after

使用 contextmanager 装饰器,不用再写一个类来实现上下文管理协议,只需要用一个方法装饰对应的方法即可。但是如果被装饰的方法内发生了异常,那么我们需要在自己的方法中进行异常处理,否则将不会执行 yield 之后的逻辑。

案例:

 from contextlib import contextmanager
 ​
 @contextmanager
 def test(a):
     print('before')
     try:
         if a == 0:
             raise Exception('分母不能为0')
         yield 1/a
     except Exception as e:
         print(e)
     finally:
         print('after')
 ​
 with test(0) as t:
     print(t)

closing方法

相当于实现了类似Cpp中析构函数的作用

 from contextlib import closing
 ​
 class Test(object):
 ​
     # 定义close方法才可以使用closing装饰器
     def close(self):
         print('closed')
 ​
 with closing(Test()) as t:
     print('test')
 ​
 """
 test
 closed
 """