python 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__() 方法。


1.基本语法和工作原理
with context_expression [as target(s)]:
        with-body
with open(r'somefileName') as somefile:
        for line in somefile:
            print line
            # ...more code

等价于传统方式, try/finally 方式操作文件对象

 somefile = open(r'somefileName')
    try:
        for line in somefile:
            print line
            # ...more code
    finally:
        somefile.close()

with 语句的执行过程类似如下代码块:

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

1.执行 context_expression,生成上下文管理器 context_manager
2.调用上下文管理器的__enter__() 方法;如果使用了 as 子句,则将__enter__() 方法的返回值赋值给 as 子句中的 target(s)
3.执行语句体 with-body
4.不管是否执行过程中是否发生了异常,执行上下文管理器的 __exit__() 方法,__exit__() 方法负责执行“清理”工作,如释放资源等。如果执行过程中没有出现异常,或者语句体中执行了语句 break/continue/return,则以 None 作为参数调用__exit__(None, None, None) ;如果执行过程中出现异常,则使用 sys.exc_info 得到的异常信息为参数调用__exit__(exc_type, exc_value, exc_traceback)
5.出现异常时,如果 __exit__(type, value, traceback) 返回 False,则会重新抛出异常,让with 之外的语句逻辑来处理异常,这也是通用做法;如果返回 True,则忽略异常,不再对异常进行处理


2.自定义上下文管理器

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

假设有一个资源 DummyResource,这种资源需要在访问前先分配,使用完后再释放掉;分配操作可以放到 enter() 方法中,释放操作可以放到 exit() 方法中。简单起见,这里只通过打印语句来表明当前的操作,并没有实际的资源分配与释放。
自定义支持 with 语句的对象

 class DummyResource:
    def __init__(self, tag):
            self.tag = tag
            print 'Resource [%s]' % tag
        def __enter__(self):
            print '[Enter %s]: Allocate resource.' % self.tag
            return self   # 可以返回不同的对象
        def __exit__(self, exc_type, exc_value, exc_tb):
            print '[Exit %s]: Free resource.' % self.tag
            if exc_tb is None:
                print '[Exit %s]: Exited without exception.' % self.tag
            else:
                print '[Exit %s]: Exited with exception raised.' % self.tag
                return False   # 可以省略,缺省的None也是被看做是False

使用自定义的支持 with 语句的对象

  with DummyResource('Normal'):
        print '[with-body] Run without exceptions.'

    with DummyResource('With-Exception'):
        print '[with-body] Run with exception.'
        raise Exception
        print '[with-body] Run with exception. Failed to finish statement-body!'

执行结果

 Resource [Normal]
    [Enter Normal]: Allocate resource.
    [with-body] Run without exceptions.
    [Exit Normal]: Free resource.
    [Exit Normal]: Exited without exception.

Resource [With-Exception]
    [Enter With-Exception]: Allocate resource.
    [with-body] Run with exception.
    [Exit With-Exception]: Free resource.
    [Exit With-Exception]: Exited with exception raised.

    Traceback (most recent call last):
      File "G:/demo", line 20, in <module>
       raise Exception
    Exception

3.contextlib 模块

contextlib 模块提供了3个对象:装饰器 contextmanager、函数 nested 和上下文管理器 closing。使用这些对象,可以对已有的生成器函数或者对象进行包装,加入对上下文管理协议的支持,避免了专门编写上下文管理器来支持 with 语句。
装饰器 contextmanager
contextmanager 用于对生成器函数进行装饰,生成器函数被装饰以后,返回的是一个上下文管理器,其 __enter__()__exit__()方法由 contextmanager 负责提供,而不再是之前的迭代子。被装饰的生成器函数只能产生一个值,否则会导致异常 RuntimeError;产生的值会赋值给 as 子句中的 target,如果使用了 as 子句的话。下面看一个简单的例子。
装饰器 contextmanager 使用示例

 from contextlib import contextmanager

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

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

** contextmanager 使用示例执行结果**

 [Allocate resources]
    Code before yield-statement executes in __enter__
    Assigned Value: *** contextmanager demo ***
    Code after yield-statement executes in __exit__
    [Free resources]

可以看到,生成器函数中 yield 之前的语句在__enter__()方法中执行,yield 之后的语句在__exit__()中执行,而 yield 产生的值赋给了 as 子句中的 value 变量。
需要注意的是,contextmanager 只是省略了__enter__() / __exit__()的编写,但并不负责实现资源的“获取”和“清理”工作;“获取”操作需要定义在 yield 语句之前,“清理”操作需要定义 yield 语句之后,这样 with 语句在执行 __enter__() / __exit__()方法时会执行这些语句以获取/释放资源,即生成器函数中需要实现必要的逻辑控制,包括资源访问出现错误时抛出适当的异常。
函数 nested
nested 可以将多个上下文管理器组织在一起,避免使用嵌套 with 语句。

 with nested(A(), B(), C()) as (X, Y, Z):
         # with-body code here

nested 执行过程

 with A() as X:
        with B() as Y:
            with C() as Z:
                 # with-body code here

需要注意的是,发生异常后,如果某个上下文管理器的 exit() 方法对异常处理返回 False,则更外层的上下文管理器不会监测到异常。
上下文管理器 closing
上下文管理 closing 实现

  class closing(object):
        # help doc here
        def __init__(self, thing):
            self.thing = thing
        def __enter__(self):
            return self.thing
        def __exit__(self, *exc_info):
            self.thing.close()

上下文管理器会将包装的对象赋值给 as 子句的 target 变量,同时保证打开的对象在 with-body 执行完后会关闭掉。closing 上下文管理器包装起来的对象必须提供 close() 方法的定义,否则执行时会报 AttributeError 错误。
自定义支持 closing 的对象

class ClosingDemo(object):
        def __init__(self):
            self.acquire()
        def acquire(self):
            print 'Acquire resources.'
        def free(self):
            print 'Clean up any resources acquired.'
        def close(self):
            self.free()

    with closing(ClosingDemo()):
        print 'Using resources'

** 自定义 closing 对象的输出结果**

Acquire resources.
    Using resources
    Clean up any resources acquired.

closing 适用于提供了 close() 实现的对象,比如网络连接、数据库连接等,也可以在自定义类时通过接口 close() 来执行所需要的资源“清理”工作。
[1]https://www.ibm.com/developerworks/cn/opensource/os-cn-pythonwith/#icomments

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容