Python上下文管理器

上下文管理协议

实现了enterexit这两个上下文管理器协议

with语句的执行原理
  1. 执行 context_exp 以获取上下文管理器
  2. 加载上下文管理器的 exit() 方法以备稍后调用
  3. 调用上下文管理器的 enter() 方法
  4. 如果有 as var 从句,则将 enter() 方法的返回值赋给 var
  5. 执行子代码块 with_suit
  6. 调用上下文管理器的 exit() 方法,如果 with_suit 的退出是由异常引发的,那么该异常的 type、value 和 traceback 会作为参数传给 exit(),否则传三个 None
  7. 如果 with_suit 的退出由异常引发,并且 exit() 的返回值等于 False,那么这个异常将被重新引发一次;如果 exit() 的返回值等于 True,那么这个异常就被无视掉,继续执行后面的代码
上下文管理器工具

Python提供了一个模块用于实现更函数式的上下文管理器用法。

contextlib.contextmanager

contextlib.contextmanager 是一个装饰器,它可以用来装饰被 yield 语句分割成两部分的函数,以此进行上下文管理。任何在yield之前的内容都可以看做在代码块执行前的操作,而任何yield之后的操作都可以看做是代码块结束后要做的操作。如果希望在上下文管理器中使用 “as” 关键字,那么就用 yield 返回你需要的值,它将通过 as 关键字赋值给新的变量。

使用 contextlib.contextmanager 时,可以大致套用如下的框架:

1
2
3
4
5
6
7
8
from contextlib import contextmanager

@contextmanager
def closing(thing):
try:
yield thing
finally:
thing.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import contextlib

@contextlib.contextmanager
def database():
db = Database()
try:
if not db.connected:
db.connect()
yield db
except Exception as e:
db.close()

def handle_query():
with database() as db:
print 'handle ---', db.query()

使用contextlib 定义一个上下文管理器函数,通过with语句,database调用生成一个上下文管理器,然后调用函数隐式的enter方法,并将结果通yield返回。最后退出上下文环境的时候,在excepit代码块中执行了exit方法。

contextlib.closing

contextlib.closing 方法在语句块结束后调用对象的 close 方法。

1
2
3
4
5
6
from contextlib import closing
import urllib

with closing(urllib.urlopen('http://www.python.org')) as page:
for line in page:
print line

contextlib.nested

contextlib.nested 方法用于替换嵌套的 with 语句。例如,有两个文件,一个读一个写,即进行拷贝。以下是不提倡的用法:

1
2
3
with open('toReadFile', 'r') as reader:
with open('toWriteFile', 'w') as writer:
writer.writer(reader.read())

这里可以用 contextlib.nested 进行优化:

1
2
3
with contextlib.nested(open('fileToRead.txt', 'r'), \
open('fileToWrite.txt', 'w')) as (reader, writer):
writer.write(reader.read())