本章讨论其他语言中不常见的一些流程控制特性,包括:
- with语句和上下文管理器
- for、while和try语句的else子句
with语句会设置一个临时上下文,交给上下文管理器对象控制,并且负责清理上下文。
15.1 先做这个,再做那个:if语句之外的else块
else子句不仅能在if语句中使用,还能在for、while语句中使用。
15.2 上下文管理器和with块
with语句的目的是简化try/finally模式。
上下文管理器协议包含__enter__和__exit__两个方法。with语句开始运行时,会在上下文管理器对象上调用__enter__方法。with语句运行结束后,会在上下文管理器对象上调用__exit__方法,以此扮演finally子句的角色。
示例15-1 演示把文件对象当成上下文管理器使用
>>> with open('mirror.py') as fp:
... src = fp.read(60)
...
>>> len(src)
60
>>> fp
<_io.TextIOWrapper name='mirror.py' mode='r' encoding='UTF-8'>
>>> fp.closed, fp.encoding
(True, 'UTF-8')
>>> fp.read(60)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.
>>>
不管控制流程以哪种方式退出with块,都会在上下文管理器对象上调用__exit__方法,而不是在__enter__方法返回的对象上调用。
示例15-2 测试LookGlass上下文管理器类
>>> from mirror import LookingGlass
>>> with LookingGlass() as what:
... print('Alice, Kitty and Snowdrop')
... print(what)
...
pordwonS dna yttiK ,ecilA
YKCOWREBBAJ
>>> what
'JABBERWOCKY'
>>> print('Back to normal')
Back to normal
示例15-3 mirror.py: LookingGlass上下文管理器类的代码
class LookingGlass:
def __enter__(self):
import sys
self.original_write = sys.stdout.write
sys.stdout.write = self.reverse_write
return 'JABBERWOCKY'
def reverse_write(self, text):
self.original_write(text[::-1])
def __exit__(self, exc_type, exc_value, traceback):
import sys
sys.stdout.write = self.original_write
if exc_type is ZeroDivisionError:
print('Please DO NOT divide by zero!')
return True
示例15-4 在with块之外使用LookingGlass类
>>> from mirror import LookingGlass
>>> manager = LookingGlass()
>>> manager
<mirror.LookingGlass object at 0x7f7fc82bc9e8>
>>> monster = manager.__enter__()
>>> monster == 'JABBERWOCKY'
eurT
>>> monster
'YKCOWREBBAJ'
>>> manager
>8e9cb28cf7f7x0 ta tcejbo ssalGgnikooL.rorrim<
>>> manager.__exit__(None, None, None)
>>> monster
'JABBERWOCKY'
15.3 contextlib模块中的实用工具
15.4 使用@contextmanager
@contextmanager装饰器能减少创建上下文管理器的样板代码量,因为不用编写一个完整的类,定义__enter__和__exit__方法。而只需实现一个yield语句的生成器,生成想让__enter__方法返回的值。
在使用@contextmanager装饰的生成器中,yield语句的作用是把函数定义体分成两部分:yield语句前面的所有代码在with块开始时(即解释器调用__enter__方法时)执行,yield语句后面的代码在with块结束时(即调用__exit__)执行。
示例15-5 mirror_gen.py:使用生成器实现的上下文管理器
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
yield 'JABBERWOCKY'
sys.stdout.write = original_write
示例15-6 测试looking_glass上下文管理器函数
>>> from mirror_gen import looking_glass
>>> with looking_glass() as what:
... print('Alice, Kitty and Snowdrop')
... print(what)
...
pordwonS dna yttiK ,ecilA
YKCOWREBBAJ
>>> what
'JABBERWOCKY'
注意,在@contextmanager装饰器的生成器中,yield与迭代没有任何关系。