with...as...

with...as...
  with...as...语句是很让人困惑的,这一节就从演变的角度分析和讲解with...as...语句。
  一旦理解了以下问题的解决方案,你就会觉得with...as...语句其实也是很简单的。
  假设有三段代码:
    代码段一:获取资源
    代码段二:实际处理
    代码段三:后期清理
  “获取资源”通常指的是打开一个文件,打开一个数据库,或者获取到一个外部的资源;“实际处理”指的是对数据的处理,例如读写文件,读写数据库,读取和修改外部资源的属性;“后期清理”指的是关闭文件,关闭数据库,或者释放一个外部资源。
  为了确保资源状态的正确性,通常我们需要无论如何都要执行“后期清理”,即无论“实际处理”中发生什么情况都要执行“后期清理”。例如,对一个文件进行操作时,我们希望:无论“实际处理”(即对文件的读写)过程中发生什么,都要关闭文件。最简单的方式是使用try...finally...语句:

获取资源
try:
    实际处理
finally:
    后期清理

  上面的设计中,finally确保了无论“实际处理”发生什么,都会执行“后期清理”。每次都手动输入try...finally...是比较繁琐的,让我们做一些改进。将try...finally...封装到公用函数alway_clear()中,将“实际处理”封装到函数handle_data(),将handle_data作为实参穿过alway_clear():

def alway_clear(handle_data):
    获取资源
    try:
        handle_data
    finally:
        后期清理

def handle_data():
    实际处理
    
alway_clear(handle_data)

  这个设计比前一个设计更方便了,但是如果“实际处理”需要修改本地变量怎么办。为了解决这个问题,就需要引入yield:

def alway_clear():
    获取资源
    try:
        通过yield将获取到的资源实例resource(文件实例、数据库连接实例,外部资源实例)传递出去
    finally:
        后期清理

for resource in alway_clear():
    实际处理:对resource的处理

  上面这个设计解决了“实际处理”中需要修改本地变量的问题,但是这个设计很丑陋,特别是居然需要引入一个for语句(循环),并且实际上for循环中总是只执行一次循环。
  经过对各种解决方案的对比和分析,Python开发者最终发明了with...as...语句,通过实例而不是yield来实现流程:

class alway_clear:
    def __enter__(self):
        获取资源resource
        return resource
        
    def __exit__(self, type, value, traceback):
        后期清理
        
with alway_clear() as resource:
    实际处理:对resource的处理

  现在我们就可以很容易地理解with...as...的执行流程了。Python会先计算with与as之间表达式的值(假设名为context_guard),然后调用context_guard的方法__entry__(),将__entry__()的返回值赋给as后的变量(此处为变量resource),执行with...as...下的语句,不管执行过程中发生什么,都会执行context_guard的方法__exit__()。
  特别的,__exit__()函数可以过滤异常。如果需要忽略某个异常,只需要返回True就可以了。下面的例子中,检测到TypeError异常,会忽略掉,不会向外抛出异常;而其它类型的异常则可以通过。

def __exit__(self, type, value, traceback):
    return isinstance(value, TypeError) # 异常类型保存在变量value中,通过isinstance()判断其是否是TypeError

  阅读完上面的内容,现在你可以很容易的理解with...as...了。

with open('test.txt') as f
    通过f读写文件

  上面的程序执行流程是这样的:打开文件test.txt,将文件对象赋给变量f,之后就可以通过f读写文件了。无论读写文件的过程中发生什么,都会执行关闭文件的操作。