1. 主页
  2. Python基础到高级
  3. 魔术方法
  4. 上下文管理

上下文管理

支持上下文管理的对象

在介绍文件IO的那篇文章中,说到了使用with的来做文件的上下文管理

In [14]: with open('test.txt') as f:
    ...:     pass
    ...:

这里open方法,返回的是一个文件对象,而文件对象是支持上下文管理的,支持上下文管理的对象是可以使用with的形式来处理数据的

在一个对象中同时出现__enter__和__exit__方法,那么这个对象就是支持上下文管理的对象

In [15]: class Context:
    ...:     def __enter__(self):    
    ...:         print('enter context')
    ...:     def __exit__(self, *args, **kwargs):
    ...:         print('exit context')
    ...:   

In [16]: with Context():   # Context对象符合了支持上下文管理的对象,因此可以使用with来处理Context对象
    ...:     print('do somethings')
    ...: print('out of context')
    ...: 
enter context
do somethings
exit context
out of context

进入with语句块之前,会执行__enter__方法,退出with语句块之前,会返还__exit__方法

为什么我们会将这种现象叫做上下文呢?

with在开启一个语句块的时候,执行这个语句块之前,会执行__enter__方法,执行这个语句块之后,会执行__exit__方法,也就是说在这个语句块的前后会执行一些操作,这样的行为叫做上下文管理

上下文管理是安全的,既是with块抛出了异常,__enter__和__exit__方法,也还是会执行的

In [1]: class Context:
   ...:     def __enter__(self):    
   ...:         print('enter context')
   ...:     def __exit__(self, *args, **kwargs):
   ...:         print('exit context')      
   ...:                       

In [2]: with Context():
   ...:     raise Exception()
   ...: 
enter context
exit context
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-2-c1afee4bfdab> in <module>()
      1 with Context():
----> 2     raise Exception()

Exception:

即使with块中主动退出解释器,抛出SystemExit异常的情况下,with块也是会执行__enter__和__exit__方法

In [3]: import sys

In [4]: with Context():
   ...:     sys.exit()
   ...:     
enter context
exit context
An exception has occurred, use %tb to see the full traceback.

SystemExit

/root/.pyenv/versions/3.6.1/envs/lanyulei/lib/python3.6/site-packages/IPython/core/interactiveshell.py:2855: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D.
  warn("To exit: use 'exit', 'quit', or Ctrl-D.", stacklevel=1)

with的as字句可以获取__enter__方法的返回值

In [5]: class Context:
   ...:     def __enter__(self):    
   ...:         print('enter context')
   ...:         return 'lanyulei'    # __enter__的返回值
   ...:     def __exit__(self, *args, **kwargs):
   ...:         print('exit context')      
   ...:                       

In [6]: with Context() as s:
   ...:     print(s)   # 打印返回值
   ...:     
enter context
lanyulei
exit context

有几点需要注意下:

  • __enter__方法除了self参数外,不带任何参数
  • __exit__的return返回值,是没有办法获取到,但是当with块中抛出异常的时候,__exit__的return返回值是False的时候会向上抛出异常,返回True会屏蔽异常

刚刚介绍了说__enter__方法除了self参数外,是不带任何参数,但是__exit__是有三个位置参数的

In [7]: class Context:
   ...:     def __enter__(self):    
   ...:         print('enter context')
   ...:         return 'lanyulei'
   ...:     def __exit__(self, exc_type, exc, traceback):   # 将三个参数写出来,分别打印出来
   ...:         print('exc_type: {}'.format(exc_type))
   ...:         print('exc: {}'.format(exc))
   ...:         print('traceback: {}'.format(traceback))      
   ...:                       

In [8]: with Context():
   ...:     raise TypeError('this is not "int"')
   ...: 
enter context
exc_type: <class 'TypeError'>   # 异常类型
exc: this is not "int"    # 异常详情
traceback: <traceback object at 0x7f7caa490988>   # 异常在内存的所在位置
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-8-b9cf849e8a46> in <module>()
      1 with Context():
----> 2     raise TypeError('this is not "int"')

TypeError: this is not "int"

上下文管理的应用场景例子

代码块运行时间计算

In [2]: import datetime

In [6]: class Timeit:
   ...:     def __enter__(self):
   ...:         self.start = datetime.datetime.now()   # 代码块开始执行的时间
   ...:     def __exit__(self, *args):
   ...:         cost = datetime.datetime.now() - self.start   # 代码块结束执行的时间
   ...:         print(cost)
   ...:         

In [7]: with Timeit():   # 这里使用with调用Timeit的类对象,就将with块的执行时间计算出来了
   ...:     z = 3 + 8
   ...:     
0:00:00.000014

上面的例子的是根据一组代码块来实现的,下面介绍一个可以对函数进行装饰的应用场景

In [9]: class Timeit:
   ...:     def __init__(self, fn=None):
   ...:         wraps(fn)(self)
   ...:     def __call__(self, *args, **kwargs):
   ...:         start = datetime.datetime.now()
   ...:         ret = self.__wrapped__(*args, **kwargs)
   ...:         cost = datetime.datetime.now() - start
   ...:         print(cost)
   ...:         return ret
   ...:     def __enter__(self):
   ...:         self.start = datetime.datetime.now()
   ...:     def __exit__(self, *args):
   ...:         cost = datetime.datetime.now() - self.start
   ...:         print(cost)

上面的例子变可以直接作用于函数了,和装饰器的用法是一样的,是不是感觉Python的魔术方法非常NB

In [11]: from functools import wraps

In [12]: @Timeit
    ...: def add(x, y):
    ...:     return x + y
    ...: 

In [13]: add(3, 5)
0:00:00.000013
Out[13]: 8

contextlib.contextmanager装饰器

contextlib.contextmanager装饰器可以将一个生成器转换一个上下文管理

In [15]: from contextlib import contextmanager   导入contextmanager装饰器

In [16]: @contextmanager
    ...: def context():
    ...:     print('enter context')   # 初始化部分,相当于__enter__方法
    ...:     try:
    ...:         yield 'function'   # 相当于 __enter__的返回值
    ...:     finally:
    ...:         print('exit context')   # 清理部分,相当于__exit__方法
    ...:         

In [17]: with context() as c:
    ...:     print(c)
    ...:     raise Exception()
    ...: 
enter context  
function
exit context
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-17-4c1dae6b647a> in <module>()
      1 with context() as c:
      2     print(c)
----> 3     raise Exception()

Exception:

上下文管理的具体应用场景

凡是在代码块前后插入代码的场景统统适用于上下文管理

  • 资源管理
  • 权限验证
  • 等等…

我们要如何帮助您?

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注