Python中的上下文管理器,实际上就是实现了上下文管理协议的对象。在Python中打开文件的时候,我们需要确保文件被使用完毕之后,对其进行关闭操作——调用文件对象的close()方法。如果不使用上下文管理器,经典的处理方式就是将close()方法的调用放在一个finally语句中:
f = open("www.log")
try:
print("do something with file")
finally:
f.close()
这里finally的唯一作用就是确保文件对象最后被正确关闭,为此我们使用了一个try...finally语句块——这就需要我们在实现业务逻辑的时候,去考虑一些业务逻辑之外的东西,我们需要一个类似于自动内存回收(GC)的机制,来自动地帮我们做文件描述符的清理,让我们把精力都用在业务逻辑实现上。上下文管理器就可以达到这样的目的。而Python的文件对象是实现了这个协议了的:
with open("www.log") as f:
print("do something with file")
这和上面的try...finally风格的代码实现了等价的功能,只不过这里的代码更加简洁了,文件对象最后也可以被关闭。
上下文管理协议的定义非常简单,就是在被上下文包覆的代码执行前执行一个__enter__方法;在被包覆代码之后执行一个__exit__方法,根据这个定义我们就能写出一个非常简单的上下文管理器:
class SimpleContext:
def __enter__(self):
print("before code block")
def __exit__(self, exc_type, exc_val, exc_tb):
print("after code block")
if __name__ == '__main__':
with SimpleContext():
print("I am code block")
运行这个代码的输出为:
before code block
I am code block
after code block
可以看到,with块下的代码执行之前,运行了上下文协议的__enter__方法,with块下的代码执行之后,运行了上下文协议的__exit__ 方法。
可以通过在__enter__方法中返回一个值,将此值定义应用在with块中,可以通过as关键词来获取对这个值的引用,这就是上面看到的with open("www.log") as f中获取对f引用的方法:
class SimpleContext:
def __enter__(self):
print("before code block")
return 100
def __exit__(self, exc_type, exc_val, exc_tb):
print("after code block")
if __name__ == '__main__':
with SimpleContext() as sc:
print(sc)
print("I am code block")
这样输出就变成了:
before code block
100
I am code block
after code block
另外可以通过__exit__方法的回调参数exc_type, exc_val, exc_tb来访问with语句块的代码执行的异常信息,如果代码块一切正常,它们都是None;如果有异常抛出,就可以通过这三个参数来获取异常信息,做相应的处理。
以上就是上下文管理器的一个完整的内容了。但是Python中,为了让实现上下文管理器的过程更加简单,提供了另一种实现上下文协议的方法,就是通过contextlib模块中的contextmanager装饰器,结合使用生成器来实现:
import contextlib
@contextlib.contextmanager
def SimpleContext():
print("before code block")
try:
yield 100
except Exception as e:
print("handle e here, not in __exit__")
finally:
print("after code block")
if __name__ == '__main__':
with SimpleContext() as sc:
print(sc)
print("I am code block")
这个实现的输出和上面的输出是一致的,只是写法的不同。第一种写法结构上更加清晰,第二种写法逻辑上更加连续,哪一种方法更好,作出自己的选择即可。
以上。