Python-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() 方法

上下文管理器类型

Python 的 with 语句支持通过上下文管理器所定义的运行时上下文这一概念。
此对象的实现使用了一对专门方法,允许用户自定义类来定义运行时上下文,在语句体被执行前进入该上下文,并在语句执行完毕时退出该上下文:

contextmanager.__enter__()
进入运行时上下文并返回此对象或关联到该运行时上下文的其他对象。
此方法的返回值会绑定到使用此上下文管理器的 with 语句的 as 子句中的标识符。

一个返回其自身的上下文管理器的例子是 file object。 
文件对象会从 __enter__() 返回其自身,以允许 open() 被用作 with 语句中的上下文表达式。


contextmanager.__exit__(exc_type, exc_val, exc_tb)
退出运行时上下文并返回一个布尔值旗标来表明所发生的任何异常是否应当被屏蔽。 
如果在执行 with 语句的语句体期间发生了异常,则参数会包含异常的类型、值以及回溯信息。 在其他情况下三个参数均为 None。

自此方法返回一个真值将导致 with 语句屏蔽异常并继续执行紧随在 with 语句之后的语句。
否则异常将在此方法结束执行后继续传播。 在此方法执行期间发生的异常将会取代 with 语句的语句体中发生的任何异常。

传入的异常绝对不应当被显式地重新引发 —— 相反地,此方法应当返回一个假值以表明方法已成功完成并且不希望屏蔽被引发的异常。 
这允许上下文管理代码方便地检测 __exit__() 方法是否确实已失败。

Python 定义了一些上下文管理器来支持简易的线程同步、文件或其他对象的快速关闭,以及更方便地操作活动的十进制算术上下文。
除了实现上下文管理协议以外,不同类型不会被特殊处理。 请参阅 contextlib 模块查看相关的示例。

@contextlib.contextmanager
decorator--装饰器
这个函数是一个 decorator ,它可以定义一个支持 with 语句上下文的工厂函数, 而不需要创建一个类或区 enter() 与 exit() 方法。

尽管许多对象原生支持使用 with 语句,但有些需要被管理的资源并不是上下文管理器,并且没有实现 close() 方法而不能使用 contextlib.closing 。

# 被装饰的函数在被调用时,必须返回一个 generator-iterator。
# 这个迭代器必须只 yield 一个值出来,这个值会被用在 with 语句中,绑定到 as 后面的变量,如果给定了的话

from contextlib import contextmanager

@contextmanager
def managed_resource(*args, **kwds):
    # Code to acquire resource, e.g.:
    resource = acquire_resource(*args, **kwds)
    try:
        yield resource
    finally:
        # Code to release resource, e.g.:
        release_resource(resource)

>>> with managed_resource(timeout=3600) as resource:
...     # Resource is released at the end of this block,
...     # even if code in the block raises an exception
当生成器发生 yield 时,嵌套在 with 语句中的语句体会被执行。 
语句体执行完毕离开之后,该生成器将被恢复执行。 
如果在该语句体中发生了未处理的异常,则该异常会在生成器发生 yield 时重新被引发。 
因此,你可以使用 try...except...finally 语句来捕获该异常(如果有的话),或确保进行了一些清理。 
如果仅出于记录日志或执行某些操作(而非完全抑制异常)的目的捕获了异常,生成器必须重新引发该异常。 
否则生成器的上下文管理器将向 with 语句指示该异常已经被处理,程序将立即在 with 语句之后恢复并继续执行

contextmanager() 使用 ContextDecorator 因此它创建的上下文管理器不仅可以用在 with 语句中,还可以用作一个装饰器。
当它用作一个装饰器时,每一次函数调用时都会隐式创建一个新的生成器实例(这使得 contextmanager() 创建的上下文管理器满足了支持多次调用以用作装饰器的需求,而非“一次性”的上下文管理器)

装饰器 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

with 语句用于包装带有使用上下文管理器 定义的方法的代码块的执行。
带有一个“项目”的 with 语句的执行过程如下:

  1. 对上下文表达式 (在 with_item 中给出的表达式) 求值以获得一个上下文管理器。
  2. 载入上下文管理器的 __enter__() 以便后续使用。
  3. 载入上下文管理器的 __exit__() 以便后续使用。
  4. 发起调用上下文管理器的 __enter__() 方法。
  5. 如果 with 语句中包含一个目标,来自 __enter__() 的返回值将被赋值给它。
  6. 注解 with 语句会保证如果 __enter__() 方法返回时未发生错误,则 __exit__() 将总是被调用。 因此,如果在对目标列表赋值期间发生错误,则会将其视为在语句体内部发生的错误。 参见下面的第 6 步。

  7. 执行语句体。
  8. 发起调用上下文管理器的 __exit__() 方法。 如果语句体的退出是由异常导致的,则其类型、值和回溯信息将被作为参数传递给 __exit__()。 否则的话,将提供三个 None 参数。

    如果语句体的退出是由异常导致的,并且来自 __exit__() 方法的返回值为假,则该异常会被重新引发。 如果返回值为真,则该异常会被抑制,并会继续执行 with 语句之后的语句。

    如果语句体由于异常以外的任何原因退出,则来自 __exit__() 的返回值会被忽略,并会在该类退出正常的发生位置继续执行。
with EXPRESSION as TARGET:
    SUITE
#等价下面的
manager = (EXPRESSION)
enter = type(manager).__enter__
exit = type(manager).__exit__
value = enter(manager)
hit_except = False

try:
    TARGET = value
    SUITE
except:
    hit_except = True
    if not exit(manager, *sys.exc_info()):
        raise
finally:
    if not hit_except:
        exit(manager, None, None, None)

自定义支持 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!'
posted @ 2021-02-01 18:37  EdenWu  阅读(141)  评论(0编辑  收藏  举报