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 中包含 breakreturn 等跳出的语句,也会转为执行 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 语句。