上下文管理器
上下文管理器对象存在的目的是管理 with 语句, 就像迭代器的存在是为了管理 for 语句一样。with 语句的目的是简化 try/finally 模式。 这种模式用于保证一段代码运行完毕后执行某项操作, 即便那段代码由于异常、 return 语句或sys.exit() 调用而中止, 也会执行指定的操作。 finally 子句中的代码通常用于释放重要的资源, 或者还原临时变更的状态。
上下文管理器协议包含 __enter__ 和 __exit__ 两个方法。 with 语句开始运行时, 会在上下文管理器对象上调用 __enter__ 方法。 with 语句运行结束后, 会在上下文管理器对象上调用 __exit__ 方法, 以此扮演 finally 子句的角色。
示例一:把文件对象当成上下文管理器使用
with open('data.py') as fp: #执行with 后面的表达式得到的结果是上下文管理器对象, 不过, 把值绑定
#到目标变量上(as 子句) 是在上下文管理器对象上调用 __enter__ 方法的结果。
src=fp.read(60)
print(src)
print(fp) #可以读取fp对象,但是不能进行I/O操作,因为在with块的末尾,调用TextIOWrapper.__exit__方法把文件给关闭了
'''
不管控制流程以哪种方式退出 with 块, 都会在上下文管理器对象上调用 __exit__ 方法, 而不是在 __enter__ 方法返回的对象上调用。
'''
示例二:
class LookingGlass:
def __enter__(self):
import sys
self.original_write = sys.stdout.write # 把原来的 sys.stdout.write 方法保存在一个实例属性中,供后面使用。
sys.stdout.write = self.reverse_write # 猴子补丁,替换字符串反转方法
return 'JABBERWOCKY' # 返回字符串,存入what
def reverse_write(self, text): # 自定义的字符串反转方法
self.original_write(text[::-1])
def __exit__(self, exc_type, exc_value, traceback):
# 如果一切正常, Python 调用 __exit__ 方法时传入的参数是 None,None, None; 如果抛出了异常, 这三个参数是异常数据
import sys
sys.stdout.write = self.original_write
if exc_type is ZeroDivisionError:
print('Please DO NOT divide by zero!')
return True
with LookingGlass() as what: # Python 在上下文管理器上调用 __enter__ 方法, 把返回结果绑定到 what 上。
print('Alice, Kitty and Snowdrop')
print(what)
结果:
解释器调用 __enter__ 方法时, 除了隐式的 self 之外, 不会传入任何参数。 传给 __exit__ 方法的三个参数列举如下。
exc_type
异常类(例如 ZeroDivisionError) 。
exc_value
异常实例。 有时会有参数传给异常构造方法, 例如错误消息, 这些
参数可以使用 exc_value.args 获取。
traceback
traceback 对象
使用@contextmanager
@contextmanager
这个装饰器把简单的生成器函数变成上下文管理器, 这样就不用创建类去实现管理器协议了。@contextmanager 装饰器能减少创建上下文管理器的样板代码量, 因为不用编写一个完整的类, 定义 __enter__ 和 __exit__ 方法, 而只需实现有一个 yield 语句的生成器, 生成想让 __enter__ 方法返回的值。
在使用 @contextmanager 装饰的生成器中, yield 语句的作用是把函数的定义体分成两部分: yield 语句前面的所有代码在 with 块开始时(即解释器调用 __enter__ 方法时) 执行, yield 语句后面的代码在with 块结束时(即调用 __exit__ 方法时) 执行。
上面的示例使用@contextmanager 装饰器
import contextlib
@contextlib.contextmanager
def looking_glass():
import sys
original_write = sys.stdout.write
def reverse_write(text):
original_write(text[::-1])
sys.stdout.write = reverse_write
msg = ''
try:
yield 'JABBERWOCKY' # 产出一个值, 这个值会绑定到 with 语句中 as 子句的目标变量上。
except ZeroDivisionError:
msg = 'Please DO NOT divide by zero!'
finally:
sys.stdout.write = original_write
if msg:
print(msg)
@contextmanager 装饰器优雅且实用, 把三个不同的 Python 特性结合到了一起: 函数装饰器、 生成器和 with 语句。