理解Python中的“with”

source 9年前
   <p> </p>    <h3>1. 缘起</h3>    <p>Python中,打开文件的操作是非常常见的,也是非常方便的,那么如何优雅的打开一个文件?大部分的同学会这样实现:</p>    <pre>  <code class="language-python">with open( "a.txt" ) as f :      # do something  </code></pre>    <p>大家都知道,这样写可以自动处理资源的释放、处理异常等,化简了我们打开文件的操作,那么, with 到底做了什么呢?</p>    <p>从《Python学习手册》中是这么描述的:</p>    <p>简而言之,with/as语句的设计是作为常见try/finally用法模式的替代方案。就像try/finally语句,with/as语句也是用于定义必须执行的终止或“清理”行为,无论步骤中是否发生异常。不过,和try/finally不同的是,with语句支持更丰富的基于对象的协议,可以为代码块定义支持进入和离开动作。</p>    <p>也就是说对于代码:</p>    <pre>  <code class="language-python">with expression [as varible]:      with-block  </code></pre>    <p>with语句的实际工作方式:</p>    <p>1.计算表达式,所得到的对象是 <strong>环境管理器</strong> ,他必须有 <strong>enter</strong> , <strong>exit</strong> 两个方法。</p>    <p>2.环境管理器的 <strong>enter</strong> 方法会被调用。如果as存在, <strong>enter</strong> 的返回值赋值给as后面的变量,否则,被丢弃。</p>    <p>3.代码块中嵌套的代码(with-block)会执行。</p>    <p>4.如果with代码块会引发异常, <strong>exit</strong> (type,value,traceback)方法就会被调用。这些也是由sys.exec <em> </em></p>    <p><em>info返回相同的值。如果此方法返回为假,则异常会重新引发。否则,异常会中止。正常情况下异常是应该被重新引发,这样的话传递到with语句外。</em></p>    <p> </p>    <p><em>5.如果with代码块没有引发异常, <em>_exit</em> </em></p>    <p>方法依然会调用,其type、value以及traceback参数会以None传递。</p>    <p>with/as语句的设计,是为了让必须在程序代码块周围发生的启动和终止活动一定会发生。和try/finally语句(无论异常是否发生其离开动作都会执行)类似,但是with/as有更丰富的对象协议,可以定义进入和离开的动作。</p>    <h3>2. 设计的初衷</h3>    <p>with/as语句的设计的初衷,在 <a href="/misc/goto?guid=4959670873790530799" rel="nofollow,noindex">PEP343</a> 中是这么描述的:</p>    <p>This PEP adds a new statement “with” to the Python language to make it possible to factor out standard uses of try/finally statements.</p>    <p>In this PEP, context managers provide <strong>enter</strong> () and <strong>exit</strong> () methods that are invoked on entry to and exit from the body of the with statement.</p>    <p>对于下面的操作:</p>    <pre>  <code class="language-python">with EXPR as VAR:              BLOCK  </code></pre>    <p>等价于</p>    <pre>  <code class="language-python">mgr = (EXPR)  exit = type(mgr).__exit__  # Not calling it yet  value = type(mgr).__enter__(mgr)  exc = True  try:      try:          # 将__enter__函数调用的返回值返回给VAR          VAR = value  # Only if "as VAR" is present          # 执行BLOCK          BLOCK      except:          # 异常处理,The exceptional case is handled here          exc = False          if not exit(mgr, *sys.exc_info()):              raise          # The exception is swallowed if exit() returns true  finally:      # 清理,The normal and non-local-goto cases are handled here      if exc:          exit(mgr, None, None, None)  </code></pre>    <p>我们可以看到上述代码完整的处理了初始化及异常/正常场景的清理操作,这便是 with 的设计思想,化简了冗余的代码,把那些重复的工作以及异常处理操作交给写“EXPR”源码(比如open操作)的同学。</p>    <h3>3. 更深入的学习</h3>    <p>我们继续深入的看下 <a href="/misc/goto?guid=4959670873872561766" rel="nofollow,noindex">Python3</a> 中 <strong>enter</strong> 和 <strong>exit</strong> 的实现:</p>    <pre>  <code class="language-python">class IOBase(metaclass=abc.ABCMeta):      # ... ...        ### Context manager ###        def __enter__(self):  # That's a forward reference          """Context management protocol. Returns self (an instance of IOBase)."""          self._checkClosed()          return self        def __exit__(self, *args):          """Context management protocol. Calls close()"""          self.close()  </code></pre>    <p>和我们预期的一致,在 <strong>enter</strong> 中返回了这个IO对象,然后在 <strong>exit</strong> 中,进行了清理。</p>    <h3>参考资料</h3>    <ol>     <li>《Python学习手册》</li>     <li><a href="/misc/goto?guid=4959549102350019982" rel="nofollow,noindex">Understanding Python’s “with” statement</a></li>     <li><a href="/misc/goto?guid=4959670873790530799" rel="nofollow,noindex">PEP 343 — The “with” Statement</a></li>     <li><a href="/misc/goto?guid=4959670873993127126" rel="nofollow,noindex">Catching an exception while using a Python ‘with’ statement</a></li>     <li><a href="/misc/goto?guid=4959669278157308821" rel="nofollow,noindex">理解Python中的with…as…语法</a></li>     <li><a href="/misc/goto?guid=4959670874099404319" rel="nofollow,noindex">PEP 3116 — New I/O</a></li>     <li><a href="/misc/goto?guid=4959670873872561766" rel="nofollow,noindex">Python 3.5.0 Code</a></li>    </ol>    <p>来自: <a href="/misc/goto?guid=4959670874193759775" rel="nofollow">http://yikun.github.io/2016/04/15/理解Python中的“with”/</a> </p>