一个熟悉的操作:
下面的操作是我们在文件处理中较为常用的一种方法,使用这种方法读写文件的时候,当with语句下的处理代码块执行完毕后会自动关闭文件读写流,而不需要f.close()
with open('/path/to/file', 'r') as f:
# <处理代码块开始>
print(f.read())
...
# <处理代码块结束>
...
这就是上下文管理器给我的第一印象,其实它的作用不止限于文件读写操作
上下文管理器是Python2.5之后才出现的概念。上下文管理器规定了某个对象的使用范围,当进入或者离开了使用范围,都会有相应的一些调用,比如代码块开始时执行一些准备,代码块结束时结束一些操作。它更多的是用于资源的分配和释放上,即在开始时分配资源,结束时释放一些资源。比如在执行数据库查询时要建立连接,查询结束后要释放连接;写文件时要先打开文件,写结束后,要关闭文件等等。还有,就是资源的加锁和解锁,比如在使用多线程时,可能会用到加锁和解锁。
上下文管理器可以通过使用更可读、更精简的代码实现资源的分配与释放。
实现自己的上下文管理器
要实现一个自定义的上下文管理器,肯定要实现两个方法,一是进入对象范围时的准备工作,二是离开对象范围时的结束工作。下面是一个可以给代码块计时的上下文管理器
import time
class Getime(object):
def __init__(self, Is_print=False):
self.Isprint = Is_print
def __enter__(self):
self.pre_time = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.cur_time = time.time()
delta_time = self.cur_time - self.pre_time
if self.Isprint:
print("the delta time is {0}".format(delta_time) + "s")
# 调用
>>> with Getime(True):
for i in range(10000000):
pass
>>> the delta time is 0.4488217830657959s
Python提供了两个类的方法分别实现上述功能:
__enter__ 进入对象范围时(一般代码块开始)被调用。配合with语句使用的时候,上下文管理器会自动调用__enter__方法,然后进入运行时上下文环境,如果有as 从句,返回自身或另一个与运行时上下文相关的对象,值赋值给as后跟的变量
__exit__ 离开对象范围时(代码块结束)被调用。当with块执行完毕退出with语句块或者with代码块出现异常,则会自动执行__exit__方法,并且会把对于的异常参数传递进来。如果__exit__函数返回True。则with语句代码块不会显示的抛出异常,终止程序,如果返回None或者False,异常会被主动raise,并终止程序。exc_type, exc_val, exc_tb 三个参数指出错误信息
(更多关于__exit__异常处理的细节问题可以参考http://python.jobbole.com/82289/)
因此,一个Python类,只要实现了上述两种方法,就可以说是一个上下文管理器。
梳理一下便是:
- 执行 contextor 以获取上下文管理器
- 加载上下文管理器的 exit() 方法以备稍后调用
- 调用上下文管理器的 enter() 方法
- 如果有 as var 从句,则将 enter() 方法的返回值赋给 var
- 执行子代码块 with_body
- 调用上下文管理器的 exit() 方法,如果 with_body 的退出是由异常引发的,那么该异常的 type、value 和 traceback 会作为参数传给 exit(),否则传三个 None
- 如果 with_body 的退出由异常引发,并且 exit() 的返回值等于 False,那么这个异常将被重9新引发一次;如果 exit() 的返回值等于 True,那么这个异常就被无视掉,继续执行后面的代码
所以在代码或函数执行的时候,调用函数时候有一个环境,在不同的环境调用,有时候效果就不一样,这些不同的环境就是上下文。例如数据库连接之后创建了一个数据库交互的上下文,进入这个上下文,就能使用连接进行查询,执行完毕关闭连接退出交互环境。创建连接和释放连接都需要有一个共同的调用环境。不同的上下文,通常见于异步的代码中。
查看当前类是否支持上下文管理器
例如我们查看文件读写类open
>>> print(dir(open))
['__call__',
'__class__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__name__',
'__ne__',
'__new__',
'__qualname__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__self__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__text_signature__']
可以看到, 列表中存在__enter__函数和__exit__函数,故open支持上下文管理器方法
方案二:
通过python提供的上下文管理工具contextlib可以更优雅的实现上下文管理器,具体参见http://python.jobbole.com/87317/