首页 > 代码库 > Python——with语句、context manager类型和contextlib库

Python——with语句、context manager类型和contextlib库

目录

  一、with语句

  二、上下文管理器

  三、contextlib模块

 

一、with语句

  关于 Python 中 with 语句的详细说明:PEP 343

  with 语句用上下文管理器定义的方法包裹一段代码的执行,等价于简单版的try...except...finally语句。with语句的主要针对的情境:不论一个代码块的执行过程是否出现异常,都要在结束的时候执行一些操作(比如清理)。

  with语句语法:

with_stmt ::=  "with" with_item ("," with_item)* ":" suitewith_item ::=  expression ["as" target]

  或:

with expression [as variable]:    with-block

  expression应该返回一个支持“上下文管理协议”的对象,如果 as 分句存在的话,这个对象的返回值会被赋给 as 后面的变量名。

 

第一种语法表示中with语句的执行流程:

  1. 计算上下文表达式 (with_item 中给出的表达式) ,获得一个上下文管理器,该上下文管理器包括__enter__() 和 __exit__()方法;
  2. 加载该上下文管理器的 __exit__() 方法留待以后使用;
  3. 调用上下文管理器的 __enter__() 方法;
  4. 如果 with 语句中的 with_item 指定了目标别名(as 分句后面的变量名),__enter__() 的返回值被赋给这个目标别名;

  *注

  with 语句确保只要 __enter__() 正常返回,那么 __exit__()一定会被调用。因此如果在将值赋给目标别名时发生错误,该错误将被当做发生在suite中。可以看下面的第6步。

  5. with语句中嵌套的 suite 部分被执行(第二种表示中的 with-block 部分);

  6. 上下文管理器的 __exit__() 方法被调用,如果是异常造成 suite(with-block) 部分退出,异常的类型、值和回溯都被当做参数传给 __exit__(type, value, traceback) 方法,这三个值和sys.exc_info的返回值相同。如果suite(with-block) 部分没有抛出异常,__exit__()的三个参数都是 None。

  如果 suite 部分是由于异常导致的退出,且__exit__()方法的返回值是false,异常将被重举;如果返回值是真,异常将被终止,with 语句后的代码继续执行。

  如果 suite 部分是由于不是异常的其他原因导致的退出,__exit__()方法的返回值被忽视,执行在退出发生的地方继续

  单个上下文管理器示例:

with open(r‘C:\misc\data‘) as myfile:    for line in myfile:        print(line)        ...more code...

  Python中的文件对象包含会在with代码段后自动关闭文件对象的上下文管理器,所以即便 for 循环中抛出处理文件对象时的异常,也能保证 myfile 引用的对象被正常关闭。

  

  多个上下文管理器被处理的方式就像多个 with 语句嵌套执行一样:

with A() as a, B() as b:    suite

  等价于:

with A() as a:    with B() as b:        suite

  多个上下文管理器示例:

with open(‘data‘) as fin, open(‘‘res‘, ‘w‘) as fout:    for line in fin:        if ‘some key‘ in line:            fout.write(line)

  

二、上下文管理器类型
  context manager 是Python中 with 语句执行时用来定义运行时上下文的对象,上下文管理器控制着 进 / 出 运行时上下文的功能,上下文管理器通常由 with 语句触发,也可以直接通过调用他们的方法来使用他们。

  上下文管理器的通常用法包括保存和恢复各式各样的全局状态、加解锁资源和关闭打开的文件等等。

  一些内置的Python类型自带上下文管理器,比如文件对象等,但是我们同样可以自己实现自己需要的上下文管理器,上下文管理器必须提供下面的两个方法:

 

 object.__enter__(self) 

  进入对象object相关的运行时上下文,如果有 as 分句的话,with 语句将会把该方法的返回值和 as 分句指定的目标(别名)进行绑定。

 

 object.__exit__(self, exc_type, exc_value, traceback) 

  退出对象object相关的运行时上下文,参数描述造成上下文退出的异常,如果上下文不是因为异常而退出,则三个参数都是 None。

  如果参数中提供了一个异常,而该方法想要抑制该异常(比如阻止其传播),那么它应该返回一个真值,否则该函数退出时会正常处理这个异常。

   __exit__() 方法不应该重新抛出被传入的异常,这是调用__exit__()的函数的工作

  

  Python的 with 语句支持由上下文管理器定义的运行时上下文的概念,由两个方法来实现,这两个方法允许用户在自定义的类中定义运行时上下文,执行流程在 with 语句开始前进入上下文,当 with 语句结束后退出。

  要定义运行时上下文,上下文管理器必须提供一对方法:

 contextmanager.__enter__() 

  进入运行时上下文,要么返回该对象,要么返回一个与运行时上限文相关的对象,如果有 as 分句的话,with 语句将会把该方法的返回值和 as 分句指定的目标(别名)进行绑定。

  比如文件对象在__enter__()里返回自己,这样 open() 函数可以被当做环境表达式在一个 with 语句中使用。

  另一种情形中,decimal.localcontext()返回一个相关的对象。上下文管理器将活跃的小数上下文设置为初始小数上下文的拷贝,然后返回拷贝后的引用。这样可以在 with 语句中修改当前的小数上下文而不影响with 语句外的代码。
 contextmanager.__exit__(exc_type, exc_val, exc_tb) 
  退出运行时上下文,返回一个Bool型的标识指示是否有异常应该被抑制,参数描述造成上下文退出的异常,如果上下文不是因为异常而退出,则三个参数都是 None。
  该方法如果返回的是 True ,会造成 with 语句抑制异常,然后直接执行 with 语句后的语句,否则异常会在该方法调用结束后正常传播,该方法执行时发生的异常会替代所有其他在执行 with 语句时出现的异常。
  传入的异常不应被显式地重举,而应该返回false表明该方法已经正常执行,而且不会抑制传入的异常,这便于上下文管理器检测__exit__()方法是否真的执行失败。
  Python定义了若干上下文管理器,Python的 generator 类型和装饰器 contextlib.contextmanager 提供了实现上下文管理器协议的手段,如果一个生成器函数被装饰器 contextlib.contextmanager所装饰,它将会返回一个实现了必要的 __enter__() 和 __exit__() 方法的上下文管理器,而不是普通的迭代器。

  Note that there is no specific slot for any of these methods in the type structure for Python objects in the Python/C API. Extension types wanting to define these methods must provide them as a normal Python accessible method. Compared to the overhead of setting up the runtime context, the overhead of a single class dictionary lookup is negligible.

   自定义上下文管理器示例:

class TraceBlock(object):    def message(self, arg):        print(‘running‘ + arg)    def __enter__(self):        print(‘starting with block‘)        return self    def __exit__(self, exc_type, exc_value, exc_tb):        if exc_type is None:            print(‘exited normally\n‘)        else:            print(‘raise an exception! ‘ + str(exc_type))            return False #Propagateif __name__ == ‘__main__‘:    with TraceBlock() as action:        action.message(‘test1‘)        print(‘reached‘)     with TraceBlock() as action:        action.message(‘test2‘)        raise TypeError        print (‘not reached‘)

  

三、contextlib模块

  该模块提供对 with 语句的支持,提供的函数有:

 contextlib.contextmanager(func) 

  装饰器,用来定义一个 with 语句的上下文管理器的工厂函数,而不必创建一个类或单独指定__enter__() 和 __exit__() 方法。

  示例

  (该例子不应该用于实际生成HTML!):

from contextlib import contextmanager@contextmanagerdef tag(name):    print "<%s>" % name    yield    print "</%s>" % name>>> with tag("h1"):...    print "foo"...<h1>foo</h1>

  被装饰的函数被调用时必须返回一个生成器-迭代器。迭代器每次产生一个值,如果有 as 分句的话,这个值用来与 with 语句中的 as 分句绑定

  在生成器 yield 的地方,嵌套在 with 语句中的代码块(with-block)被执行,生成器在代码块退出后继续执行,如果在代码块中有任何没有被处理的异常抛出,它会在生成器中 yield 处重举,因此你可以使用 try...except...finally 语句来处理错误,或者确保完成某些清理工作。如果捕获异常的目标是记录日志或执行一些操作(而不是抑制它),生成器必须重举异常。否则生成器上下文管理器将会告知 with 语句说异常已经被处理了,但是一旦执行到 with 语句后的代码,异常会立即继续。

 

 contextlib.closing(thing) 

  返回一个上下文管理器,在完成代码块的执行时关闭参数 thing。等价于:

from contextlib import contextmanager@contextmanagerdef closing(thing):    try:        yield thing    finally:        thing.close()

  可以这么使用:

from contextlib import closingimport urllibwith closing(urllib.urlopen(‘http://www.python.org‘)) as page:    for line in page:        print line

  该例中,不用显式地关闭 page。即使发生错误,也会在 with 代码块退出时执行 page.close() 

Python——with语句、context manager类型和contextlib库