Python中的with语句和上下文管理器
2014-06-10
Python 已有2089人围观

with语句是从Python 2.5开始引入的一种与异常处理相关的功能(2.5版本中要通过from __future__ import with_statement导入后才可以使用,从2.6 版本开始缺省可用)。with语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。要使用 with语句,首先要明白上下文管理器这一概念。有了上下文管理器,with语句才能工作。下面是一组与上下文管理器和with语句有关的概念。


上下文管理协议(Context Management Protocol):包含方法__enter__()和 __exit__(),支持该协议的对象要实现这两个方法。

上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了

__enter__() 和 __exit__() 方法。上下文管理器定义执行with语句时要建立的运行时上下文,负责执行with语句块上下文中的进入与退出操作。通常使用with语句调用上下文管理器,也可以通过直接调用其方法来使用。

运行时上下文(runtime context):由上下文管理器创建,通过上下文管理器的 __enter__()和__exit__()方法实现,__enter__()方法在语句体执行之前进入运行时上下文,__exit__()在语句体执行完后从运行时上下文退出。with语句支持运行时上下文这一概念。

上下文表达式(Context Expression):with语句中跟在关键字with之后的表达式,该表达式要返回一个上下文管理器对象。

语句体(with-body):with语句包裹起来的代码块,在执行语句体之前会调用上下文管

理器的 __enter__() 方法,执行完语句体之后会执行 __exit__() 方法。


上下文管理器(Context Manager)

上下文管理器是在Python 2.5加入的语法,用于定义某个对象的使用范围,当进入或者离开该使用范围的时候,会有特殊的操作被调用,比如为对象分配或释放内存。上下文管理器能够让你的代码可读性更强并且错误更少。

with语句

1、with语句的语法格式

with context_expression [as var]:
    with-body

这里context_expression要返回一个上下文管理器对象,该对象并不赋值给as子句中的var ,如果指定了as子句的话,会将上下文管理器的__enter__()方法的返回值赋值给var。var可以是单个变量,或者由“()”括起来的元组(不能是仅仅由“,”分隔的变量列表,必须加“()”)。

2、with语句操作文件对象

Python对一些内建对象进行改进,加入了对上下文管理器的支持,可以用于with语句中,比如可以自动关闭文件、线程锁的自动获取和释放等。比如我们要对一个文件进行操作,可以使用with语句这样实现:

with open(file_name) as some_file:
    for line in some_file:
        print line
        # other code...

使用with语句操作文件,可以避免因为忘记关闭文件句柄或者文件读取异常导致的一系列问题,同时也可以避免使用try/finally语句而导致的代码复杂冗余的问题。另外,使用with语句操作文件,不会将整个文件内容一次性加载到内存中,在对某些大文件进行操作的时候非常方便。

3、with语句的执行过程

PEP 0343对with语句的实现进行了描述,执行过程解释如下,比如:

with EXPR as VAR:
    WIRH_BLOCK

实际的执行过程是:

mgr = EXPR
exit = type(mgr).__exit__  
value = type(mgr).__enter__(mgr)
exc = True   # True表示正常执行,即便有异常也忽略;False表示重新抛出异常,需要对异常进行处理
try:
    try:
        VAR = value    # 如果使用了as子句
        WIRH_BLOCK     # 执行 WIRH_BLOCK
    except:
        # 执行过程中有异常发生
        exc = False
        # 如果__exit__返回True,则异常被忽略;如果返回False,则重新抛出异常
        # 由外层代码对异常进行处理
        if not exit(mgr, *sys.exc_info()):
            raise
finally:
    # 正常退出,或者通过statement-body中的break/continue/return语句退出
    # 或者忽略异常退出
    if exc:
        exit(mgr, None, None, None) 
    # 缺省返回None

自定义上下文管理器

要实现支持上下文管理协议的类,我们必须要实现两个方法:__enter__()和__exit__(),前者负责进入语句块的准备操作,后者负责离开语句块的善后操作。

    1、__enter__和__exit__()方法

__enter__():进入上下文管理器的运行时上下文,在语句体执行前调用。如果使用了as语句,with语句会将该方法的返回值赋值给as语句中的var;

    __exit__(exc_type, exc_value, exc_traceback):用来退出与上下文管理器相关的运行时上下文,该函数返回一个布尔值表示是否需要对发生的异常进行处理。参数表示引起退出操作的异常,如果退出时没有发生异常,则3个参数都为None。如果发生异常,返回True表示不处理异常,否则会在退出该方法后重新抛出异常以由with语句之外的代码逻辑进行处理。如果该方法内部产生异常,则会取代由statement-body中语句产生的异常。要处理异常时,不要显示重新抛出异常,即不能重新抛出通过参数传递进来的异常,只需要将返回值设置为False就可以了。之后,上下文管理代码会检测是否 __exit__() 失败来处理异常。

    2、自定义支持with语句的对象

    我们尝试使用上下文管理器实现一个类似open函数的class,代码如下:

class OpenFile:
 
    def __init__(self, file_name, mode):
        self.file_name = file_name
        self.mode = mode
 
    def __enter__(self):
        self.opened_file = open(self.file_name, self.mode)
        return self.opened_file
 
    def __exit__(self, exc_type, exc_value, exc_tb):
        self.opened_file.close()
        if exc_tb is None:
            print 'Exited without exception.'
        else:
            print 'Exited with exception raised.'
 
with OpenFile(name, mode) as f:
    f.write("Custom Context Manager!")

Python的contextlib

contextlib是Python的一个模块,用来提供更为方便的上下文管理器。

1、contextmanager装饰器

contextmanager用于对生成器函数进行装饰,生成器函数被装饰以后,返回的是一个上下文管理器,其__enter__()和__exit__()方法由contextmanager负责提供,而不再是之前的迭代子。被装饰的生成器函数只能产生一个值,否则会导致异常RuntimeError;产生的值会赋值给as语句中的VAR。

from contextlib import contextmanager

@contextmanager
def demo():
    print 'Code before yield-statement executes in __enter__'
    yield '*** contextmanager demo ***'
    print 'Code after yield-statement executes in __exit__'

with demo() as value:
    print 'Value: %s' % value

2、nested函数

nested函数的作用是将多个上下文管理器组织在一起,避免使用嵌套with语句。

with nested(test1(), test2(), test3()) as (A, B, C):
     # WITH_BLOCK

3、closing函数

假设我们有一个创建数据库函数,它将返回一个数据库对象,并且在使用完之后关闭相关资源(数据库连接会话等),我们可以像以往那样处理或是通过上下文管理器:

with contextlib.closing(CreateDatabase()) as database:
    database.query()


参考文章

PEP 343 -- The "with" Statement

浅谈 Python 的 with 语句

Python中的上下文管理器

理解Python的With语句


Over!

本文地址:http://xianglong.me/article/learn-python-6-with-statement-contextlib/

特别声明:本站文章,如非注明,皆为降龙原创。转载需注明本文链接并保证链接可用。