Python 上下文管理器和 else 块(上下文管理器和with块)

上下文管理器和with块

上下文管理器对象存在的目的是管理 with 语句,就像迭代器的存在是
为了管理 for 语句一样。

with 语句的目的是简化 try/finally 模式。这种模式用于保证一段代
码运行完毕后执行某项操作,即便那段代码由于异常、return 语句或
sys.exit() 调用而中止,也会执行指定的操作。finally 子句中的代
码通常用于释放重要的资源,或者还原临时变更的状态。

上下文管理器协议包含 __enter__ __exit__ 两个方法。with 语句
开始运行时,会在上下文管理器对象上调用 __enter__ 方法。with 语
句运行结束后,会在上下文管理器对象上调用 __exit__ 方法,以此扮
演 finally 子句的角色。

最常见的例子是确保关闭文件对象。使用 with 语句关闭文件的详细说
明参见示例 15-1。

示例 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 "", line 1, in <module>
ValueError: I/O operation on closed file.

❶ fp 绑定到打开的文件上,因为文件的 __enter__ 方法返回 self。
❷ 从 fp 中读取一些数据。
❸ fp 变量仍然可用。
❹ 可以读取 fp 对象的属性。
❺ 但是不能在 fp 上执行 I/O 操作,因为在 with 块的末尾,调用
TextIOWrapper.__exit__ 方法把文件关闭了。

示例 15-1 中标注❶的那行代码道出了不易察觉但很重要的一点:执行
with 后面的表达式得到的结果是上下文管理器对象,不过,把值绑定
到目标变量上(as 子句)是在上下文管理器对象上调用 __enter__
法的结果。

碰巧,示例 15-1 中的 open() 函数返回 TextIOWrapper 类的实例,而
该实例的 __enter__ 方法返回 self。不过,__enter__ 方法除了返回
上下文管理器之外,还可能返回其他对象。

不管控制流程以哪种方式退出 with 块,都会在上下文管理器对象上调
__exit__ 方法,而不是在 __enter__ 方法返回的对象上调用。

with 语句的 as 子句是可选的。对 open 函数来说,必须加上 as 子
句,以便获取文件的引用。不过,有些上下文管理器会返回 None,因
为没什么有用的对象能提供给用户。

示例 15-2 使用一个精心制作的上下文管理器执行操作,以此强调上下
文管理器与 __enter__ 方法返回的对象之间的区别。

示例 15-2 测试 LookingGlass 上下文管理器类

>>> 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.

❶ 上下文管理器是 LookingGlass 类的实例;Python 在上下文管理器
上调用 __enter__ 方法,把返回结果绑定到 what 上。
❷ 打印一个字符串,然后打印 what 变量的值。
❸ 打印出的内容是反向的。
❹ 现在,with 块已经执行完毕。可以看出,__enter__ 方法返回的值
——即存储在 what 变量中的值——是字符串 ‘JABBERWOCKY’。
❺ 输出不再是反向的了。

示例 15-3 是 LookingGlass 类的实现。
示例 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 ➓
⓫

❶ 除了 self 之外,Python 调用 __enter__ 方法时不传入其他参数。
❷ 把原来的 sys.stdout.write 方法保存在一个实例属性中,供后面使用。
❸ 为 sys.stdout.write 打猴子补丁,替换成自己编写的方法。
❹ 返回 ‘JABBERWOCKY’ 字符串,这样才有内容存入目标变量 what。
❺ 这是用于取代 sys.stdout.write 的方法,把 text 参数的内容反
转,然后调用原来的实现。
❻ 如果一切正常,Python 调用 __exit__ 方法时传入的参数是 None,
None, None;如果抛出了异常,这三个参数是异常数据,如下所述。
❼ 重复导入模块不会消耗很多资源,因为 Python 会缓存导入的模块。
❽ 还原成原来的 sys.stdout.write 方法。
❾ 如果有异常,而且是 ZeroDivisionError 类型,打印一个消息……
❿ ……然后返回 True,告诉解释器,异常已经处理了。
⓫ 如果 __exit__ 方法返回 None,或者 True 之外的值,with 块中的
任何异常都会向上冒泡。

解释器调用 __enter__ 方法时,除了隐式的 self 之外,不会传入任何
参数。传给 __exit__ 方法的三个参数列举如下。

exc_type
  异常类(例如 ZeroDivisionError)。

exc_value

异常实例。有时会有参数传给异常构造方法,例如错误消息,这些
参数可以使用 exc_value.args 获取。

traceback
  traceback 对象。

上下文管理器的具体工作方式参见示例 15-4。在这个示例中,我们在
with 块之外使用 LookingGlass 类,因此可以手动调用 __enter__
__exit__ 方法。

示例 15-4 在 with 块之外使用 LookingGlass 类

>>> from mirror import LookingGlass
>>> manager = LookingGlass()>>> manager
<mirror.LookingGlass object at 0x2a578ac>
>>> monster = manager.__enter__()>>> monster == 'JABBERWOCKY' ➌
eurT
>>> monster
'YKCOWREBBAJ'
>>> manager
>ca875a2x0 ta tcejbo ssalGgnikooL.rorrim<
>>> manager.__exit__(None, None, None)>>> monster
'JABBERWOCKY'

❶ 实例化并审查 manager 实例。
❷ 在上下文管理器上调用 __enter__() 方法,把结果存储在 monster
中。
❸ monster 的值是字符串 ‘JABBERWOCKY’。打印出的 True 标识符是反向的,因为 stdout 的所有输出都经过 __enter__ 方法中打补丁的
write 方法处理。
❹ 调用 manager.__exit__,还原成之前的 stdout.write。
上下文管理器是相当新颖的特性,Python 社区肯定还在不断寻找新的创
意用法。标准库中有一些示例。

你可能感兴趣的:(流程Python,python,java,前端)