with 语句会设置一个临时的上下文,交给上下文管理器对象控制,并负责清理上下文。
15.1 if 语句之外的 else 块
for/else:仅当 for 循环运行完毕时(没有被 break 语句终止)才运行 else 块;
while/else:仅当因条件为假而退出时(没有被 break 语句终止)才运行 else 块;
- 以上的循环结构+
else 子句可用于替代控制标志(flag)与额外的条件判断;
try/else:仅当没有异常抛出时才运行 else 块。其中的异常不会再被 except 捕捉;
- 以上的结构可以让
try 块中只包含抛出预期异常的语句,把后续处理放在 else 中;
- Python 官方定义的编程风格:
- EAFP:假定输入满足特定的条件,按照预期逻辑处理,使用
try 语句捕获异常;
- LBYL:预先使用
if 语句显式测试前提。这可能会在多线程环境下的条件竞争中出错。
15.2 上下文管理器和 with 块
try-except-finally 语句:无论 try 中是否发生异常,finally 块中的语句一定会在最后执行。即使在 try 中包含 break、return 等跳出的语句,也会转为执行 finally 再跳出。由此来看,finally 子句可以用于释放重要资源或还原临时状态;
with 语句可以简化 try/finally 模式:开始运行时,在上下文管理器对象上调用 __enter__ 方法;结束运行后,在上下文管理器对象上调用 __exit__ 方法;
with 块没有定义新的作用域;
- 在
with 语句中,执行 with 后面的表达式得到的结果是上下文管理器对象,后面的 as 子句(可选)是将 __enter__ 方法的返回值绑定到目标变量(特别地,TextIOWrapper 实例的 __enter__ 方法返回自身 self)。
'''
>>> from mirror import LookingGlass
>>> with LookingGlass() as what: # <1>
... print('Alice, Kitty and Snowdrop') # <2>
... print(what)
...
pordwonS dna yttiK ,ecilA # <3>
YKCOWREBBAJ
>>> what # <4>
'JABBERWOCKY'
>>> print('Back to normal.') # <5>
Back to normal.
'''
class LookingGlass:
def __enter__(self): # <1>
import sys
self.original_write = sys.stdout.write # <2>
sys.stdout.write = self.reverse_write # <3> monkey-patch
return 'JABBERWOCKY' # <4>
def reverse_write(self, text): # <5>
self.original_write(text[::-1])
def __exit__(self, exc_type, exc_value, traceback): # <6>
import sys # <7>
sys.stdout.write = self.original_write # <8>
if exc_type is ZeroDivisionError: # <9>
print('Please DO NOT divide by zero!')
return True # <10>
# <11>
__enter__ 方法的参数仅有隐式的 self;__exit__ 方法的参数还有异常相关信息(这些参数就是 finally 块中 sys.exc_info() 的结果),正常情况下均为 None:
exc_type:异常类对象;
exc_value:异常的实例,可用 exc_value.args 获取信息;
traceback:异常抛出的 traceback 对象;
- 如果
__exit__ 返回 True,则说明异常被正确处理,不再继续上浮。否则会向上冒泡;
- 也可以直接显式调用
__enter__、__exit__ 方法来手动控制上下文;
- 小技巧:Python 会缓存导入的模块,故在不同作用域内重复导入模块不会消耗较多资源。
15.3 contextlib 模块中的实用工具
closing(obj) 可以基于对象已经实现的 close() 方法构建上下文管理器;
suppress 可构建临时忽略指定异常的上下文管理器;
@contextmanager 可以将简单的生成器函数变成上下文管理器。
- 继承自基类
ContextDecorator 定义得到的上下文管理器可以用作函数装饰器;
ExitStack 上下文管理器可以管理多个上下文管理器,按照 LIFO 的顺序退出上下文。
15.4 使用 @contextmanager
yield 语句的作用是将函数的定义体分为两部分:前半部分在开始时执行,后半部分在结束时执行。这种作用更像是协程。yield 产出的值会被绑定到 as 子句的目标变量上;
- 直接调用该函数即可得到上下文管理器;
- 实现原理:
contextmanager 装饰器会把这个生成器函数包装成上下文管理器类。在开始时创建生成器实例,执行 next() 并返回产出值;在结束时检查并向生成器抛出异常(使用 gen.throw(exception),在生成器函数中 yield 的那一行),否则执行 next() 完成操作;
- 为了防止异常导致终止执行,要在
yield 语句处使用 try-except-finally 捕捉异常:使用 except 处理需要处理的异常,让其他异常向上冒泡,并在 finally 块中进行上下文重置;
contextmanager 提供的 __exit__ 会默认抛给生成器的异常都被正确处理了,只有当有未处理的异常时(生成器显式抛出异常/异常上浮)才会被继续抛出。
15.5 本章小结
@contextmanager 装饰器把三个不同的 Python 特性结合到了一起:函数装饰器、生成器和 with 语句。