第十五章 - 上下文管理器和else块
上下文管理器和else块
在使用Python编程中,可以会经常碰到这种情况:有一个特殊的语句块,在执行这个语句块之前需要先执行一些准备动作;当语句块执行完成后,需要继续执行一些收尾动作。
例如:当需要操作文件或数据库的时候,首先需要获取文件句柄或者数据库连接对象,当执行完相应的操作后,需要执行释放文件句柄或者关闭数据库连接的动作。
又如,当多线程程序需要访问临界资源的时候,线程首先需要获取互斥锁,当执行完成并准备退出临界区的时候,需要释放互斥锁。
对于这些情况,Python中提供了上下文管理器(Context Manager)的概念,可以通过上下文管理器来定义/控制代码块执行前的准备动作,以及执行后的收尾动作。
15.1 先做这个,再做那个:if语句之外的else块
else子句的行为如下:
for
仅当for循环运行完毕时(没有被break终止),才运行else块。
while
仅当while循环因为条件为假而退出时(没有被break终止),才运行else块。
try
仅当try块中没有异常抛出时才运行else块。
15.2 上下文管理器和with块
上下文管理器对象存在的目的是管理with语句,就像迭代器的存在是为了管理for语句。with语句的目的是检查try/finally模式。
上下文管理器协议包含 __enter__ 和 __exit__ 两个方法,with语句在开始运行时会在上下文管理器对象上调用 __enter__方法,with语句运行结束后,会在上下文管理器对象上调用 __exit__ 方法。
示例15-2 Looking上下文管理器类的代码
class Looking(object): def __enter__(self): import sys self.original_write = sys.stdout.write sys.stdout.write = self.reverse_write # 为sys.stdout.write打猴子补丁,替换成自己编写的方法 return "JACK" def reverse_write(self, text): self.original_write(text[::-1]) def __exit__(self, exc_type, exc_val, exc_tb): import sys sys.stdout.write = self.original_write if exc_type is ZeroDivisionError: print("Please Do Not divide by zero!") return True # 如果__exit__方法返回None,或者True之外的值,with块中的任何异常都会向上冒泡 with Looking() as what: print("Alice, Kitty and Snowdrop") print(what) >>>> pordwonS dna yttiK ,ecilA KCAJ print(what) >>>> JACK
15.3 contextlib模块中的实用工具
closing, suppress, ContextDecorator, ExitStack, @contextmanager
使用最广泛的是 @contextmanager装饰器,这个装饰器也有迷惑人的一面,因为它与迭代无关,却要使用yield语句。
15.4 使用@contextmanager
@contextmanager装饰器能减少创建上下文管理器的样板代码量,因为不用编写一个完整的类(定义 __enter__ __exit__方法),只需要实现由一个yield语句的生成器,生成想让__enter__方法返回的值。
在使用@contextmanager装饰的生成器中,yield语句的作用是把函数的定义体分成两部分:yield语句前面的所有代码在with块开始时(即调用__enter__方法时)执行,yield语句后面的代码在with块结束时(即调用__exit__方法时)执行。
示例15-4 使用生成器实现的上下文管理器
*contextlib.contextmanager装饰器会把函数包装成实现__enter__ __exit__方法的类。
import contextlib @contextlib.contextmanager def looking(): import sys original_write = sys.stdout.write def reverse_write(text): original_write(text[::-1]) sys.stdout.write = reverse_write yield "JACK" sys.stdout.write = original_write with looking() as what: print("Alex, Jack, Kitty") print(what) >>>> yttiK ,kcaJ ,xelA KCAJ print(what) >>>> JACK
@contextmanager装饰器优雅且实用,把三个不同的Python特性结合到了一起:函数装饰器、生成器、with语句。