支持上下文管理的对象
在介绍文件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:
上下文管理的具体应用场景
凡是在代码块前后插入代码的场景统统适用于上下文管理
- 资源管理
- 权限验证
- 等等…