<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 本章讨论其他语言不常见的流程控制，用户可能会忽略这些特性：
with 语句和上下文管理器
for while 和 try 语句的 else 子句
with 语句会设置一个临时的上下文，交给上下文管理器对象控制，并负责清理上下文。这么做能避免错误并减少样板代码，因此 API 更安全，更易于使用。除了自动关闭文件之外，with 块还有很多用途
else 子句和 with 没关系，不过这两个都内容比较短，所以放到了一个逻辑
先做这个，再做那个： if 之外的 else 块
else 子句不仅能在 if 语句中使用，还能在 for，while，try 语句中使用
else 子句行为如下：
for
Step1:  一开始你可能觉得没必要在 try/except 中使用 else 子句，毕竟下面代码中只有 dangerous_cal() 不抛出异常 after_call() 才会执行
Step2:  然而，after_call() 不应该放在 try 块中。为了清晰准确，try 块应该只抛出预期异常的语句，因此像下面这样写更好：
Step3:  现在很明确，try 为了捕获的是 dangerous_call() 的异常。
Python 中，try/except 不仅用于处理错误，还用于控制流程，为此，官方定义了几个缩略词：
EAFP
Step4:  with 的 as 子句是可选的，对 open 来说，必须加 as 子句，以便获取文件的引用。不过，有些上下文管理器会返回 None，因为没有什么有用的对象能提供给用户
下面是一个精心制作的上下文管理器执行操作，以此强调上下文管理器与 __enter__ 方法返回的对象之间的区别
Step5:  在实际应用中，如果程序接管了标准输出，可能会把 sys.stdout 换成类似文件的其他对象，然后再切换成原来的版本。contextlib.redirect_stdout 上下文管理器就是这么做的
解释器调用 enter 方法时，除了隐式的 self 之外，不会传入任何参数，传给 __exit__ 的三个参数如下：
exc_type
Step6:  上面在命令行执行的，因为在 jupyter notebook 的输出有时候有莫名其妙的 bug
contextlib 模块中的实用工具
自定义上下文管理器类之前，先看一下 Python 标准库文档中的 contextlib。除了前面提到的 redirect_stdout 函数，contextlib 模块中还有一些类和其它函数，实用范围更广
closing
Step7:  其实，contextlib.contextmanager 装饰器会把函数包装成实现 __enter__ 和 __exit__ 方法的类
这个类的 __enter__ 作用如下：
调用生成器函数，保存生成器对象（这里称为 gen）
调用 next(gen)，执行到 yield 关键字位置
返回 next(gen) 产生的值，以便把产生的值绑定到 with/as 语句中目标变量上
with 块终止时，__exit__ 方法会做以下几件事
检查有没有把异常传给 exc_type, 如果有，调用 gen.throw(exception), 在生成器函数定义体中包含 yield 关键字的那一行跑出异常
否则，调用 next(gen)，继续执行生成器函数体中 yield 语句之后的代码
上面的例子其实有一个严重的错误，如果在 with 块中抛出了异常，Python 解释器会将其捕获，然后在 looking_glass 函数的 yield 表达式再次跑出，但是，那里没有处理错误的代码，因此 looking_glass 函数会终止，永远无法恢复成原来的 sys.stdout.write 方法，导致系统处于无效状态，下面添加了一些代码，用于处理 ZeroDivisionError 异常，这样就比较健壮了
Step8:  前面说过，为了告诉解释器异常已经处理了，__exit__ 方法返回 True，此时解释器会压制异常。如果 __exit__ 方法没有显式返回一个值，那么解释器得到的是  None，然后向上冒泡异常。使用 @contextmanager 装饰器时，默认行为是相反的，装饰器提供的 __exit__ 方法假定发给生成器的所有异常都得到处理了，因此应该压制异常。如果不想让 @contextmanager 压制异常，必须在装饰器的函数中显式重新跑出异常
把异常发给生成器的方式是使用 throw 方法，下章讲
这样的约定的原因是，创建上下文时，生成器无法返回值，只能产出值。不过现在可以返回值了，见下章
使用 @contextmanager 装饰器时，要把 yield 语句放到 try/finally 语句中（或者放在 with 语句中），这是无法避免的，因为我们永远不知道上下文管理器用户会在 with 块中做什么
除了标准库中举得例子外，Martijin Pieters 实现原地文件重写上下文管理器是 @contextmanager 不错的使用实例，如下：
<ASSISTANT_TASK:>
Python Code:
# for item in my_list:
#     if item.flavor == 'banana':
#         break
# else:
#     raise ValueError('No banana flavor found!')
Explanation: 本章讨论其他语言不常见的流程控制，用户可能会忽略这些特性：
with 语句和上下文管理器
for while 和 try 语句的 else 子句
with 语句会设置一个临时的上下文，交给上下文管理器对象控制，并负责清理上下文。这么做能避免错误并减少样板代码，因此 API 更安全，更易于使用。除了自动关闭文件之外，with 块还有很多用途
else 子句和 with 没关系，不过这两个都内容比较短，所以放到了一个逻辑
先做这个，再做那个： if 之外的 else 块
else 子句不仅能在 if 语句中使用，还能在 for，while，try 语句中使用
else 子句行为如下：
for: 仅当 for 循环运行完毕时（即 for 循环没有被 break 语句终止）才运行 else
try: 仅当 try 块中没有异常时候才运行 else 块，else 子句抛出的异常不会由前面的 except 子句处理
在所有情况下，如果异常或者 return, break 或 continue 语句导致控制权跳到了复合语句之外，else 也会被跳过
for 循环用 else 如下：
End of explanation
# try:
#     dangerous_call()
#     after_call()
# except OSError:
#     log('OSError...')
Explanation: 一开始你可能觉得没必要在 try/except 中使用 else 子句，毕竟下面代码中只有 dangerous_cal() 不抛出异常 after_call() 才会执行
End of explanation
# try:
#     dangerous_call()
# except OSError:
#     log('OSError...')
# else:
#     after_call()
Explanation: 然而，after_call() 不应该放在 try 块中。为了清晰准确，try 块应该只抛出预期异常的语句，因此像下面这样写更好：
End of explanation
with open('with.ipynb') as fp:
    src = fp.read(60)
len(src)
fp
fp.closed, fp.encoding
# fp 虽然可用，但不能执行 I/O 操作，
# 因为在 with 末尾，调用 TextIOWrapper.__exit__ 关闭了文件
fp.read(60) 
Explanation: 现在很明确，try 为了捕获的是 dangerous_call() 的异常。
Python 中，try/except 不仅用于处理错误，还用于控制流程，为此，官方定义了几个缩略词：
EAFP:
    取得原谅比获得许可容易(easier to ask for forgiveness than
permission)。这是一种常见的 Python 编程风格,先假定存在有效
的键或属性,如果假定不成立,那么捕获异常。这种风格简单明
快,特点是代码中有很多 try 和 except 语句。与其他很多语言一
样(如 C 语言),这种风格的对立面是 LBYL 风格。
LBYL
  三思而后行(look before you leap)。这种编程风格在调用函数
或查找属性或键之前显式测试前提条件。与 EAFP 风格相反,这种
风格的特点是代码中有很多 if 语句。在多线程环境中,LBYL 风
格可能会在“检查”和“行事”的空当引入条件竞争。例如,对 if
key in mapping: return mapping[key] 这段代码来说,如果
在测试之后,但在查找之前,另一个线程从映射中删除了那个键,
那么这段代码就会失败。这个问题可以使用锁或者 EAFP 风格解
决。
如果选择使用 EAFP 风格,那就要更深入地了解 else 子句，并在 try/except 中合理使用
上下文管理器和 with 块
上下文管理器对象存在的目的是管理 with 语句，就像迭代器存在是为了管理 for 语句。
with 语句目的是为了简化 try/finally 模式。上下文管理器协议包含 __enter__ 和 __exit__ 方法，with 开始时，会调用 __enter__ 方法，结束时候会调用 __exit__ 方法
最常见的是打开文件：
End of explanation
class LookingGlass:
    def __enter__(self): # enter 只有一个 self 参数
        import sys
        self.original_write = sys.stdout.write # 保存供日后使用
        sys.stdout.write = self.reverse_write # 打猴子补丁，换成自己方法
        return 'JABBERWOCKY' # 返回的字符串讲存入 with 语句的 as 后的变量
    
    def reverse_write(self, text): #取代 sys.stdout.write，反转 text
        self.original_write(text[::-1])
        
    # 正常传的参数是 None， None， None，有异常传如下异常信息
    def __exit__(self, exc_type, exc_value, traceback):
        import sys # 重复导入不会消耗很多资源，Python 会缓存导入模块
        sys.stdout.write = self.original_write # 还原 sys.stdout.write 方法
        if exc_type is ZeroDivisionError: # 如果有除 0 异样，打印消息
            print('Please DO NOT divide by zero')
            return True # 返回 True 告诉解释器已经处理了异常
# 如果 __exit__ 方法返回 None，或者 True 之外的值，with 块中的任何异常都会向上冒泡
with LookingGlass() as what:
    print('Alice, Kitty and Snowdrop') #打印出的内容是反向的
    print(what)
# with 执行完毕，可以看出 __enter__ 方法返回的值 -- 即存储在 what 变量中的值是 'JABBERWOCKY' 
what 
print('Back to normal') # 输出不再是反向的了
Explanation: with 的 as 子句是可选的，对 open 来说，必须加 as 子句，以便获取文件的引用。不过，有些上下文管理器会返回 None，因为没有什么有用的对象能提供给用户
下面是一个精心制作的上下文管理器执行操作，以此强调上下文管理器与 __enter__ 方法返回的对象之间的区别
End of explanation
# In [2]: manager = LookingGlass()
#    ...: manager
#    ...: 
# Out[2]: <__main__.LookingGlass at 0x7f586d4aa1d0>
# In [3]: monster = manager.__enter__()
# In [4]: monster == 'JABBERWOCKY'
# Out[4]: eurT
# In [5]: monster
# Out[5]: 'YKCOWREBBAJ'
# In [6]: manager.__exit__(None, None, None)
# In [7]: monster
# Out[7]: 'JABBERWOCKY'
Explanation: 在实际应用中，如果程序接管了标准输出，可能会把 sys.stdout 换成类似文件的其他对象，然后再切换成原来的版本。contextlib.redirect_stdout 上下文管理器就是这么做的
解释器调用 enter 方法时，除了隐式的 self 之外，不会传入任何参数，传给 __exit__ 的三个参数如下：
exc_type: 异常类（例如 ZeroDivisionError）
exc_value: 异常实例。有时好有参数传给异常构造方法，例如错误消息，参数可以通过 exc_value.args 获取
traceback： traceback 对象
上下文管理器具体工作方式如下：
End of explanation
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
    # 产生一个值，这个值会绑定到 with 语句的 as 子句后的目标变量上
    # 执行 with 块中的代码时，这个函数会在这一点暂停
    yield 'JABBERWOCKY' 
    # 控制权一旦跳出 with 块，继续执行 yield 语句后的代码
    sys.stdout.write = original_write
with looking_glass() as what:
    print('Alice, Kitty and Snowdrop')
    print(what)
Explanation: 上面在命令行执行的，因为在 jupyter notebook 的输出有时候有莫名其妙的 bug
contextlib 模块中的实用工具
自定义上下文管理器类之前，先看一下 Python 标准库文档中的 contextlib。除了前面提到的 redirect_stdout 函数，contextlib 模块中还有一些类和其它函数，实用范围更广
closing: 如过对象提供了 close() 方法，但没有实现 __enter__/__exit__ 协议，可以实用这个函数构建上下文管理器
suppress: 构建临时忽略指定异常的上下文管理器
@contextmanager: 这个装饰器把简单的生成器函数变成上下文管理器，这样就不用创建类去实现管理协议了
ContextDecorator: 这是个基类，用于定义基于类的上下文管理器。这种上下文管理器也能用于装饰函数，在受管理的上下文中运行整个函数
ExitStack: 这个上下文管理器能进入多个上下文管理器，with 块结束时，ExitStack 按照后进先出的顺序调用栈中各个上下文管理器的 __exit__ 方法。如果事先不知道 with 块要进入多少个上下文管理器，可以使用这个类。例如同时打开任意一个文件列表中的所有文件
这些工具中使用最广泛的是 @contextmanager 装饰器，因此要格外小心，这个装饰器也有迷惑人的一面，因为它与迭代无关，却使用 yield 语句，由此可以引出协程
使用 @contextmanager
@contextmanager 装饰器能减少创建上下文管理器的样板代码量，因为不用编写一个完整的类，定义 __enter__ 和 __exit__ 方法，而只需实现一个有 yield 语句的生成器，生成想让 __enter__ 方法返回的值
在使用 @contextmanager 装饰器能减少创建上下文管理器的样板代码量，因为不用编写一个完整的类，定义 __enter__ 和 __exit__ 方法，而只需实现有一个 yield 语句的生成器，生成想让 __enter__ 方法返回的值
在使用 @contextmanager 装饰器的生成器中，yield 语句的作用是把函数的定义体分成两个部分：yield 语句前面所有代码在 with 块开始时（即解释器调用 __enter__ 方法时）执行，yield 语句后面的代码在 with 块结束时（即调用 __exit__ 方法时）执行
End of explanation
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
    msg = ''
    try:
        yield 'JABBERWOCKY' 
    except ZeroDivisionError:
        msg = 'Please DO NOT divide by zero'
    finally:
        sys.stdout.write = original_write
        if msg:
            print(msg)
Explanation: 其实，contextlib.contextmanager 装饰器会把函数包装成实现 __enter__ 和 __exit__ 方法的类
这个类的 __enter__ 作用如下：
调用生成器函数，保存生成器对象（这里称为 gen）
调用 next(gen)，执行到 yield 关键字位置
返回 next(gen) 产生的值，以便把产生的值绑定到 with/as 语句中目标变量上
with 块终止时，__exit__ 方法会做以下几件事
检查有没有把异常传给 exc_type, 如果有，调用 gen.throw(exception), 在生成器函数定义体中包含 yield 关键字的那一行跑出异常
否则，调用 next(gen)，继续执行生成器函数体中 yield 语句之后的代码
上面的例子其实有一个严重的错误，如果在 with 块中抛出了异常，Python 解释器会将其捕获，然后在 looking_glass 函数的 yield 表达式再次跑出，但是，那里没有处理错误的代码，因此 looking_glass 函数会终止，永远无法恢复成原来的 sys.stdout.write 方法，导致系统处于无效状态，下面添加了一些代码，用于处理 ZeroDivisionError 异常，这样就比较健壮了
End of explanation
# import csv
# with inplace(csvfilename, 'r', newline='') as (infh, outfh):
#     reader = csv.reader(infh)
#     writer = csv.writer(outfh)
#     for row in reader:
#         row += ['new', 'columns']
#         writer.writerow(row)
Explanation: 前面说过，为了告诉解释器异常已经处理了，__exit__ 方法返回 True，此时解释器会压制异常。如果 __exit__ 方法没有显式返回一个值，那么解释器得到的是  None，然后向上冒泡异常。使用 @contextmanager 装饰器时，默认行为是相反的，装饰器提供的 __exit__ 方法假定发给生成器的所有异常都得到处理了，因此应该压制异常。如果不想让 @contextmanager 压制异常，必须在装饰器的函数中显式重新跑出异常
把异常发给生成器的方式是使用 throw 方法，下章讲
这样的约定的原因是，创建上下文时，生成器无法返回值，只能产出值。不过现在可以返回值了，见下章
使用 @contextmanager 装饰器时，要把 yield 语句放到 try/finally 语句中（或者放在 with 语句中），这是无法避免的，因为我们永远不知道上下文管理器用户会在 with 块中做什么
除了标准库中举得例子外，Martijin Pieters 实现原地文件重写上下文管理器是 @contextmanager 不错的使用实例，如下：
End of explanation
<END_TASK>