Chapter 15 Context Managers and else Blocks
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语句显式测试前提。这可能会在多线程环境下的条件竞争中出错。
- EAFP:假定输入满足特定的条件,按照预期逻辑处理,使用
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语句。