上下文管理器和else块
一、if 语句之外的 else块
else 子句不仅能在 if 语句中使用,还能在for、while和try语句中使用。
(1)for :仅当 for 循环运行完毕时(即 for 循环没有被break语句中止)才运行else 块,
(2)while :仅当 while 循环因为条件为False 而退出时(即 while 循环没有被break语句中止)才运行else 块
(3)try :仅当 try 块中没有异常抛出时才运行 else 块。else子句抛出的异常不会由前面的except子句处理。
▲ 在所有情况下,如果异常或者return、break或continue语句导致控制权跳到了复合语句的主块之外,else语句也会被跳过。
二、上下文管理器和 with 块
上下文管理器对象存在的目的是管理with语句,就像迭代器的存在是为了管理for语句一样。
with 语句的目的是简化 try/finally 模式。
这种模式用于保证一段代码运行完毕后执行某项操作,即便那段代码由于异常、return语句或sys.exit() 调用而中止,也会执行指定的操作。
finally 子句中代码通常用于释放重要的资源,或者还原临时变更的转态。
上下文管理器协议包含 __enter__ 和 __exit__ 两个方法。
with语句开始运行时,会在上下文管理器对象上调用__enter__方法。
with语句结束运行时,会在上下文管理器对象上调用__exit__ 方法,以此扮演finally子句的角色。
▲ 与函数的模块不同,with块没有定义新的作用域。
上下文管理器类代码:
class LookingGlass: def __enter__(self): import sys self.original_write = sys.stdout.write sys.stdout.write = self.reverse_write return 'ABCDEFGHIJK' 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 with LookingGlass() as what: print('Alice,Kitty and Snowdrop') print(what) # pordwonS dna yttiK,ecilA # KJIHGFEDCBA
__enter__方法返回的对象,存放在with语句的 as xxx对象中。
如果__exit__方法返回None,或者True之外的值,with块中的任何异常都会向上冒泡。
传给__exit__方法的三个参数列举:exc_type:异常类;exc_value:异常实例;traceback:traceback对象
三、使用 @contextmanager
@contextmanager 装饰器能减少创建上下文管理器的样板代码量;
因为不用编写一个完整的类,定义__enter__和__exit__方法,而只需实现有一个yield 语句的生成器,生成想让__enter__方法返回的值。
在使用@contextmanager 装饰的生成器中,yield语句作用是把函数的定义体分成两部分:
yield语句前面的所有代码在with块开始时执行,yield语句后面的代码在with块结束时执行。
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 'ABCDEFGHIJK' sys.stdout.write = original_write with looking_glass() as what: print('Alice,Kitty and Snowdrop') print(what) # pordwonS dna yttiK,ecilA # KJIHGFEDCBA
▲ 如果在with块中抛出异常,Python解释器会将其捕获,然后在yield表达式里再次抛出。需要在此处处理异常。
@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 'ABCDEFGHIJK' except ZeroDivisionError: msg = 'Please Do not divide by zero' finally: sys.stdout.write = original_write if msg: print(msg)
▲ 使用@contextmanager 装饰器时,要把yield语句放在 try/finally 语句中,或者放在with语句中。