Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 343 – “with” 语句

作者:
Guido van Rossum, Alyssa Coghlan
状态:
最终
类型:
标准跟踪
创建:
2005年5月13日
Python 版本:
2.5
历史记录:
2005年6月2日, 2005年10月16日, 2005年10月29日, 2006年4月23日, 2006年5月1日, 2006年7月30日

目录

摘要

此 PEP 向 Python 语言添加了一个新的语句 “with”,以便可以将 try/finally 语句的标准用法分解出来。

在此 PEP 中,上下文管理器提供 __enter__()__exit__() 方法,它们在进入和退出 with 语句主体时被调用。

作者注

此 PEP 最初由 Guido 以第一人称形式撰写,随后由 Alyssa (Nick) Coghlan 更新,以反映后来在 python-dev 上的讨论。任何第一人称参考文献均来自 Guido 的原文。

Python 的 alpha 版本发布周期揭示了此 PEP 以及相关文档和实现 [13] 中的术语问题。PEP 在第一个 Python 2.5 beta 版本发布前后稳定下来。

是的,在某些地方动词时态混乱。我们已经在这个 PEP 上工作了一年多了,所以最初属于将来的事情现在已经过去了 :)

引言

在经过大量关于 PEP 340 和替代方案的讨论后,我决定撤回 PEP 340 并提出了对 PEP 310 的一个略微变化。在更多讨论之后,我添加了一个机制,可以使用 throw() 方法在挂起的生成器中引发异常,以及一个 close() 方法,该方法会引发新的 GeneratorExit 异常;这些添加最初是在 python-dev 上的 [2] 中提出的,并得到了一致认可。我还将关键字更改为 “with”。

在接受此 PEP 后,以下 PEP 由于重叠而被拒绝。

  • PEP 310,可靠的获取/释放对。这是最初的 with 语句提案。
  • PEP 319,Python 同步/异步块。它的用例可以通过提供合适的 with 语句控制器来覆盖:对于 “同步”,我们可以使用示例 1 中的 “锁定” 模板;对于 “异步”,我们可以使用类似的 “解锁” 模板。我不认为代码块中有一个 “匿名” 锁定非常重要;事实上,始终明确使用哪个互斥锁可能更好。

PEP 340PEP 346 也与此 PEP 重叠,但在提交此 PEP 时自愿撤回了。

在 Python Wiki [3] 上对早期版本的此 PEP 进行了一些讨论。

动机和概述

PEP 340,匿名块语句,结合了许多强大的想法:使用生成器作为块模板、向生成器添加异常处理和终结,以及更多。除了赞誉之外,它还遭到了很多反对,因为人们不喜欢它在幕后实际上是一个(潜在的)循环结构。这意味着块语句中的 break 和 continue 会中断或继续块语句,即使它用作非循环资源管理工具。

但是,当我读到 Raymond Chen 关于流程控制宏的咆哮 [1] 时,最终的打击出现了。Raymond 有力地论证了将流程控制隐藏在宏中会使代码难以理解,我认为他的论点适用于 Python,也适用于 C。我意识到 PEP 340 模板可以隐藏各种控制流;例如,它的示例 4 (auto_retry()) 会捕获异常并重复该块最多三次。

但是,我认为 PEP 310 的 with 语句并不会隐藏控制流:虽然 finally 子句暂时挂起控制流,但最终,控制流会恢复,就好像 finally 子句不存在一样。

请记住,PEP 310 大致提出了以下语法(“VAR =” 部分是可选的)。

with VAR = EXPR:
    BLOCK

这大致转换为以下内容。

VAR = EXPR
VAR.__enter__()
try:
    BLOCK
finally:
    VAR.__exit__()

现在考虑这个例子。

with f = open("/etc/passwd"):
    BLOCK1
BLOCK2

在这里,就像第一行是 “if True” 一样,我们知道如果 BLOCK1 完成没有异常,就会到达 BLOCK2;如果 BLOCK1 引发异常或执行非本地 goto(break、continue 或 return),则不会到达 BLOCK2。with 语句末尾添加的魔法不会影响这一点。

(你可能会问,如果 __exit__() 方法中的错误导致异常会怎么样?那么一切都将丢失——但这与其他异常没什么区别;异常的本质是它们可以在任何地方发生,你只能接受这一点。即使你编写了无错误代码,KeyboardInterrupt 异常仍然会导致它在任何两个虚拟机操作码之间退出。)

这个论点几乎让我认可了 PEP 310,但我从 PEP 340 的狂喜中还留着一个想法,我还没有准备好放弃:使用生成器作为 “模板” 来实现抽象,比如获取和释放锁或打开和关闭文件,这是一个强大的想法,可以通过查看该 PEP 中的示例来了解这一点。

受到 Phillip Eby 对 PEP 340 的一个反提案的启发,我尝试创建一个装饰器,它会将合适的生成器转换为具有必要 __enter__()__exit__() 方法的对象。在这里我遇到了一个障碍:虽然对于锁定示例来说并不困难,但对于打开示例来说是不可能的。想法是像这样定义模板

@contextmanager
def opening(filename):
    f = open(filename)
    try:
        yield f
    finally:
        f.close()

并像这样使用它

with f = opening(filename):
    ...read data from f...

问题是,在 PEP 310 中,调用 EXPR 的结果直接分配给 VAR,然后在退出 BLOCK1 时调用 VAR__exit__() 方法。但在这里,VAR 显然需要接收打开的文件,这意味着 __exit__() 必须是文件上的一个方法。

虽然可以使用代理类来解决这个问题,但这很笨拙,让我意识到稍微不同的翻译会让编写所需的装饰器变得轻而易举:让 VAR 接收调用 __enter__() 方法的结果,并将 EXPR 的值保存起来以稍后调用它的 __exit__() 方法。然后,装饰器可以返回一个包装类实例,该实例的 __enter__() 方法调用生成器的 next() 方法并返回 next() 返回的任何内容;包装类实例的 __exit__() 方法再次调用 next(),但它期望它引发 StopIteration。(有关详细信息,请参见下面的可选生成器装饰器部分。)

因此,现在最后的障碍是 PEP 310 的语法

with VAR = EXPR:
    BLOCK1

具有欺骗性,因为 VAR不会接收 EXPR 的值。借鉴 PEP 340,很容易做到

with EXPR as VAR:
    BLOCK1

进一步的讨论表明,人们真的很喜欢能够 “看到” 生成器中的异常,即使只是为了记录它;生成器不允许生成另一个值,因为 with 语句不应可用作循环(引发不同的异常是可以接受的)。为了实现这一点,提出了一个新的 throw() 方法用于生成器,该方法接受一到三个参数,以通常的方式表示异常(类型、值、回溯)并在生成器挂起的位置引发异常。

有了这个,提出另一个生成器方法 close() 只是一个很小的步骤,它使用一个特殊异常 GeneratorExit 调用 throw()。这告诉生成器退出,从那里到提出在生成器被垃圾回收时自动调用 close() 又是一个很小的步骤。

然后,最后,我们可以允许 try-finally 语句中存在 yield 语句,因为我们现在可以保证 finally 子句将(最终)被执行。通常的关于终结的注意事项适用——进程可能会突然终止而没有终结任何对象,并且对象可能会因应用程序中的循环或内存泄漏而永远保持活动状态(与 Python 实现中的循环或泄漏相反,Python 实现中的循环或泄漏由 GC 处理)。

请注意,我们并没有保证 finally 子句在生成器对象变为未使用后立即执行,即使这将是它在 CPython 中的工作方式。这类似于自动关闭文件:虽然像 CPython 这样的引用计数实现会在最后一个引用消失后立即释放对象,但使用其他 GC 算法的实现不会做出相同的保证。这适用于 Jython、IronPython,可能还适用于运行在 Parrot 上的 Python。

(对生成器所做的更改的详细信息现在可以在 PEP 342 中找到,而不是在当前的 PEP 中)

用例

请参阅文末的示例部分。

规范:“with” 语句

提出一个新的语句,语法如下:

with EXPR as VAR:
    BLOCK

这里,“with” 和 “as” 是新的关键字;EXPR 是一个任意表达式(但不是表达式列表),而 VAR 是一个单一的赋值目标。它**不能**是逗号分隔的变量序列,但它**可以**是**带括号**的逗号分隔的变量序列。(这种限制使语法未来扩展到拥有多个逗号分隔的资源成为可能,每个资源都有自己的可选的 as 子句。)

“as VAR” 部分是可选的。

上述语句的翻译如下:

mgr = (EXPR)
exit = type(mgr).__exit__  # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
    try:
        VAR = value  # Only if "as VAR" is present
        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)

这里,小写变量(mgr、exit、value、exc)是内部变量,用户无法访问;它们很可能会实现为特殊寄存器或堆栈位置。

上述翻译的细节旨在规定确切的语义。如果找不到任何相关的两个方法,解释器将按尝试顺序(__exit____enter__)引发 AttributeError。类似地,如果任何调用引发异常,效果与上述代码中完全相同。最后,如果 BLOCK 包含 break、continue 或 return 语句,则 __exit__() 方法将使用三个 None 参数调用,就像 BLOCK 正常完成一样。(即,这些“伪异常”不被 __exit__() 视为异常。)

如果省略了语法中的“as VAR” 部分,则省略“VAR =” 部分的翻译(但仍会调用 mgr.__enter__())。

调用 mgr.__exit__() 的约定如下。如果通过 BLOCK 的正常完成或非本地跳转(BLOCK 中的 break、continue 或 return 语句)到达 finally-suite,则 mgr.__exit__() 将使用三个 None 参数调用。如果通过在 BLOCK 中引发的异常到达 finally-suite,则 mgr.__exit__() 将使用三个参数调用,分别表示异常类型、值和跟踪。

重要:如果 mgr.__exit__() 返回“真”值,则异常将被“吞没”。也就是说,如果它返回“真”,则执行将继续执行 with 语句后的下一条语句,即使 with 语句内部发生了异常。但是,如果 with 语句通过非本地跳转(break、continue 或 return)离开,则在 mgr.__exit__() 返回时,无论返回值如何,都会恢复此非本地返回。这个细节的动机是使 mgr.__exit__() 能够吞没异常,而不会太容易(因为默认返回值 None 为假,这会导致异常被重新引发)。吞没异常的主要用例是使编写 @contextmanager 装饰器成为可能,以便装饰器生成器中的 try/except 块的行为与生成器的正文在 with 语句的位置内联扩展的行为完全相同。

将异常详细信息传递给 __exit__() 的动机(而不是 PEP 310 中的无参数 __exit__())是由下面的示例 3 中的 transactional() 用例给出的。该示例中的模板必须根据是否发生异常来提交或回滚事务。我们不是简单地使用一个布尔标志来指示是否发生了异常,而是传递了完整的异常信息,以便于例如异常记录设施使用。依靠 sys.exc_info() 获取异常信息被拒绝;sys.exc_info() 的语义非常复杂,而且它完全有可能返回很久以前捕获的异常的信息。还建议添加一个额外的布尔值来区分到达 BLOCK 的末尾和非本地跳转。这被拒绝了,因为它过于复杂且没有必要;就数据库事务回滚决策而言,应将非本地跳转视为非异常情况。

为了便于直接操作上下文管理器的 Python 代码中上下文的链接,__exit__() 方法**不应该**重新引发传递给它们的错误。在这种情况下,始终是 __exit__() 方法的**调用者**的责任进行任何重新引发。

这样,如果调用者需要知道 __exit__() 调用是否**失败**(与成功清理并在传播原始错误之前成功清理相反),它就可以这样做。

如果 __exit__() 在没有错误的情况下返回,那么这可以解释为 __exit__() 方法本身的成功(无论是否要传播或抑制原始错误)。

但是,如果 __exit__() 向其调用者传播异常,这意味着 __exit__() **本身**已经失败。因此,__exit__() 方法应避免引发错误,除非它们实际上已失败。(允许原始错误继续不是失败。)

过渡计划

在 Python 2.5 中,只有当存在 future 语句时,才会识别新的语法

from __future__ import with_statement

这将使“with”和“as”成为关键字。如果没有 future 语句,将“with”或“as”用作标识符将导致向 stderr 发出警告。

在 Python 2.6 中,始终会识别新的语法;“with”和“as”始终是关键字。

生成器装饰器

随着 PEP 342 的接受,编写一个装饰器成为可能,该装饰器使使用仅产生一次的生成器来控制 with 语句成为可能。以下是这种装饰器的草图

class GeneratorContextManager(object):

   def __init__(self, gen):
       self.gen = gen

   def __enter__(self):
       try:
           return self.gen.next()
       except StopIteration:
           raise RuntimeError("generator didn't yield")

   def __exit__(self, type, value, traceback):
       if type is None:
           try:
               self.gen.next()
           except StopIteration:
               return
           else:
               raise RuntimeError("generator didn't stop")
       else:
           try:
               self.gen.throw(type, value, traceback)
               raise RuntimeError("generator didn't stop after throw()")
           except StopIteration:
               return True
           except:
               # only re-raise if it's *not* the exception that was
               # passed to throw(), because __exit__() must not raise
               # an exception unless __exit__() itself failed.  But
               # throw() has to raise the exception to signal
               # propagation, so this fixes the impedance mismatch
               # between the throw() protocol and the __exit__()
               # protocol.
               #
               if sys.exc_info()[1] is not value:
                   raise

def contextmanager(func):
   def helper(*args, **kwds):
       return GeneratorContextManager(func(*args, **kwds))
   return helper

此装饰器可以按如下方式使用

@contextmanager
def opening(filename):
   f = open(filename) # IOError is untouched by GeneratorContext
   try:
       yield f
   finally:
       f.close() # Ditto for errors here (however unlikely)

此装饰器的健壮实现将成为标准库的一部分。

标准库中的上下文管理器

可以为某些对象(如文件、套接字和锁)赋予 __enter__()__exit__() 方法,以便代替编写

with locking(myLock):
    BLOCK

可以简单地编写

with myLock:
    BLOCK

我认为我们应该谨慎对待这一点;它可能会导致类似以下的错误

f = open(filename)
with f:
    BLOCK1
with f:
    BLOCK2

这并没有做到人们期望的那样(在进入 BLOCK2 之前关闭了 f)。

OTOH 这种错误很容易诊断;例如,上面的生成器上下文装饰器在第二个 with 语句再次调用 f.__enter__() 时会引发 RuntimeError。如果在已关闭的文件对象上调用 __enter__,则可以引发类似的错误。

对于 Python 2.5,以下类型已被识别为上下文管理器

- file
- thread.LockType
- threading.Lock
- threading.RLock
- threading.Condition
- threading.Semaphore
- threading.BoundedSemaphore

还将在 decimal 模块中添加一个上下文管理器,以支持在 with 语句的正文中使用本地十进制算术上下文,在退出 with 语句时自动恢复原始上下文。

标准术语

本 PEP 建议将由 __enter__()__exit__() 方法组成的协议称为“上下文管理协议”,并将实现该协议的对象称为“上下文管理器”。[4]

语句中紧跟在 with 关键字后的表达式是一个“上下文表达式”,因为该表达式提供了关于上下文管理器为语句主体持续时间建立的运行时环境的主要线索。

with 语句主体中的代码以及 as 关键字后的变量名(或名称)在此时还没有专门的术语。如果术语不清楚,可以使用通用术语“语句主体”和“目标列表”,并在前面加上“with”或“with 语句”。

鉴于 decimal 模块的算术上下文等对象的存在,术语“上下文”不幸地存在歧义。如果需要,可以通过使用术语“上下文管理器”来指代上下文表达式创建的具体对象,以及使用术语“运行时上下文”或(最好是)“运行时环境”来指代上下文管理器所做的实际状态修改,从而使术语更具体。在简单地讨论 with 语句的使用时,歧义可能没有那么重要,因为上下文表达式完全定义了对运行时环境所做的更改。当讨论 with 语句本身的机制以及如何实际实现上下文管理器时,这种区别就更为重要了。

缓存上下文管理器

许多上下文管理器(如文件和基于生成器的上下文)将是单次使用的对象。一旦调用了 __exit__() 方法,上下文管理器将不再处于可使用状态(例如,文件已关闭,或者底层生成器已完成执行)。

对于每个 with 语句都需要一个新的管理器对象,这是避免多线程代码和嵌套的 with 语句试图使用同一个上下文管理器的最简单方法。所有支持重用的标准库上下文管理器都来自 threading 模块并非偶然 - 它们都是为了处理线程化和嵌套使用带来的问题而设计的。

这意味着为了保存具有特定初始化参数的上下文管理器以便在多个 with 语句中使用,通常需要将它存储在一个零参数的可调用对象中,然后在每个语句的上下文表达式中调用该可调用对象,而不是直接缓存上下文管理器。

当此限制不适用时,受影响的上下文管理器的文档应明确说明这一点。

已解决的问题

以下问题已通过 BDFL 批准(以及 python-dev 上没有重大反对意见)得到解决。

  1. 当底层生成器迭代器行为不当时,GeneratorContextManager应该抛出什么异常?以下引述是Guido选择在PEP 342中使用RuntimeError作为生成器close()方法和此异常类型的原因(来自[8])。

    “我宁愿不为这个目的引入一个新的异常类,因为它不是我想要人们捕捉到的异常:我希望它变成一个由程序员看到的回溯,然后程序员修复代码。所以现在我相信它们都应该抛出RuntimeError。有一些先例:它是在检测到无限递归时以及在未初始化对象时(以及各种杂项情况下)由核心 Python 代码抛出的。”

  2. 如果在 with 语句中涉及的类上没有相关方法,抛出AttributeError而不是TypeError是可以的。抽象对象 C API 抛出TypeError而不是AttributeError,是历史的偶然,而不是经过深思熟虑的设计决策[11]
  3. 具有__enter__/__exit__方法的对象被称为“上下文管理器”,将生成器函数转换为上下文管理器工厂的装饰器是contextlib.contextmanager。在 2.5 版本发布周期中,还有一些其他的建议[15],但没有令人信服的理由来改变 PEP 实现中使用的术语。

被拒绝的选项

几个月来,PEP 禁止抑制异常,以避免隐藏的流程控制。实现表明这是一个非常大的问题,所以 Guido 恢复了这个功能[12]

PEP 的另一个引发无数问题和术语争论的方面是提供一个类似于可迭代对象的__iter__()方法的__context__()方法[5] [7] [9]。解释它是什么、为什么存在、以及它应该如何工作的持续问题[10] [12],最终导致 Guido 完全放弃了这个概念[14](并且引起了很多欢呼!)。

直接使用PEP 342 生成器 API 来定义 with 语句的想法也被简要地考虑过[6],但很快就被否决,因为它使得编写非生成器 based 上下文管理器变得太困难。

示例

基于生成器的示例依赖于PEP 342。此外,一些示例在实践中是不必要的,因为合适的对象,例如threading.RLock,可以直接在 with 语句中使用。

示例上下文名称中使用的时态不是随意的。当名称指的是在__enter__方法中执行并在__exit__方法中撤消的操作时,使用过去时(“-ed”)。当名称指的是要在__exit__方法中执行的操作时,使用进行时(“-ing”)。

  1. 用于确保在块开始时获取的锁在块退出时释放的模板
    @contextmanager
    def locked(lock):
        lock.acquire()
        try:
            yield
        finally:
            lock.release()
    

    用法如下

    with locked(myLock):
        # Code here executes with myLock held.  The lock is
        # guaranteed to be released when the block is left (even
        # if via return or by an uncaught exception).
    
  2. 用于打开文件的模板,确保在块退出时关闭文件
    @contextmanager
    def opened(filename, mode="r"):
        f = open(filename, mode)
        try:
            yield f
        finally:
            f.close()
    

    用法如下

    with opened("/etc/passwd") as f:
        for line in f:
            print line.rstrip()
    
  3. 用于提交或回滚数据库事务的模板
    @contextmanager
    def transaction(db):
        db.begin()
        try:
            yield None
        except:
            db.rollback()
            raise
        else:
            db.commit()
    
  4. 示例 1 不使用生成器重写
    class locked:
       def __init__(self, lock):
           self.lock = lock
       def __enter__(self):
           self.lock.acquire()
       def __exit__(self, type, value, tb):
           self.lock.release()
    

    (此示例很容易修改以实现其他相对无状态的示例;它表明如果不需要保留任何特殊状态,则很容易避免使用生成器。)

  5. 临时重定向 stdout
    @contextmanager
    def stdout_redirected(new_stdout):
        save_stdout = sys.stdout
        sys.stdout = new_stdout
        try:
            yield None
        finally:
            sys.stdout = save_stdout
    

    用法如下

    with opened(filename, "w") as f:
        with stdout_redirected(f):
            print "Hello world"
    

    当然,这不是线程安全的,但手动进行相同的操作也不是线程安全的。在单线程程序中(例如,在脚本中),这是一种流行的做法。

  6. 一个opened()的变体,它也返回一个错误条件
    @contextmanager
    def opened_w_error(filename, mode="r"):
        try:
            f = open(filename, mode)
        except IOError, err:
            yield None, err
        else:
            try:
                yield f, None
            finally:
                f.close()
    

    用法如下

    with opened_w_error("/etc/passwd", "a") as (f, err):
        if err:
            print "IOError:", err
        else:
            f.write("guido::0:0::/:/bin/sh\n")
    
  7. 另一个有用的示例是阻塞信号的操作。用法可能如下
    import signal
    
    with signal.blocked():
        # code executed without worrying about signals
    

    一个可选参数可能是要阻塞的信号列表;默认情况下阻塞所有信号。实现留给读者作为练习。

  8. 此功能的另一个用途是 Decimal 上下文。以下是一个简单的示例,由 Michael Chermside 发布
    import decimal
    
    @contextmanager
    def extra_precision(places=2):
        c = decimal.getcontext()
        saved_prec = c.prec
        c.prec += places
        try:
            yield None
        finally:
            c.prec = saved_prec
    

    示例用法(改编自 Python 库参考)

    def sin(x):
        "Return the sine of x as measured in radians."
        with extra_precision():
            i, lasts, s, fact, num, sign = 1, 0, x, 1, x, 1
            while s != lasts:
                lasts = s
                i += 2
                fact *= i * (i-1)
                num *= x * x
                sign *= -1
                s += num / fact * sign
        # The "+s" rounds back to the original precision,
        # so this must be outside the with-statement:
        return +s
    
  9. 以下是一个用于 decimal 模块的简单上下文管理器
    @contextmanager
    def localcontext(ctx=None):
        """Set a new local decimal context for the block"""
        # Default to using the current context
        if ctx is None:
            ctx = getcontext()
        # We set the thread context to a copy of this context
        # to ensure that changes within the block are kept
        # local to the block.
        newctx = ctx.copy()
        oldctx = decimal.getcontext()
        decimal.setcontext(newctx)
        try:
            yield newctx
        finally:
            # Always restore the original context
            decimal.setcontext(oldctx)
    

    示例用法

    from decimal import localcontext, ExtendedContext
    
    def sin(x):
        with localcontext() as ctx:
            ctx.prec += 2
            # Rest of sin calculation algorithm
            # uses a precision 2 greater than normal
        return +s # Convert result to normal precision
    
    def sin(x):
        with localcontext(ExtendedContext):
            # Rest of sin calculation algorithm
            # uses the Extended Context from the
            # General Decimal Arithmetic Specification
        return +s # Convert result to normal context
    
  10. 一个通用的“对象关闭”上下文管理器
    class closing(object):
        def __init__(self, obj):
            self.obj = obj
        def __enter__(self):
            return self.obj
        def __exit__(self, *exc_info):
            try:
                close_it = self.obj.close
            except AttributeError:
                pass
            else:
                close_it()
    

    这可以用来确定性地关闭任何具有 close 方法的对象,无论是文件、生成器还是其他对象。它甚至可以在对象没有保证需要关闭时使用(例如,接受任意可迭代对象的函数)

    # emulate opening():
    with closing(open("argument.txt")) as contradiction:
       for line in contradiction:
           print line
    
    # deterministically finalize an iterator:
    with closing(iter(data_source)) as data:
       for datum in data:
           process(datum)
    

    (Python 2.5 的 contextlib 模块包含此上下文管理器的版本)

  11. PEP 319 给出了一个用例,即也需要一个released()上下文来临时释放先前获取的锁;这可以通过交换acquire()release()调用来类似于上面的 locked 上下文管理器编写
    class released:
      def __init__(self, lock):
          self.lock = lock
      def __enter__(self):
          self.lock.release()
      def __exit__(self, type, value, tb):
          self.lock.acquire()
    

    示例用法

    with my_lock:
        # Operations with the lock held
        with released(my_lock):
            # Operations without the lock
            # e.g. blocking I/O
        # Lock is held again here
    
  12. 一个“嵌套”上下文管理器,它会自动从左到右嵌套提供的上下文,以避免过度的缩进
    @contextmanager
    def nested(*contexts):
        exits = []
        vars = []
        try:
            try:
                for context in contexts:
                    exit = context.__exit__
                    enter = context.__enter__
                    vars.append(enter())
                    exits.append(exit)
                yield vars
            except:
                exc = sys.exc_info()
            else:
                exc = (None, None, None)
        finally:
            while exits:
                exit = exits.pop()
                try:
                    exit(*exc)
                except:
                    exc = sys.exc_info()
                else:
                    exc = (None, None, None)
            if exc != (None, None, None):
                # sys.exc_info() may have been
                # changed by one of the exit methods
                # so provide explicit exception info
                raise exc[0], exc[1], exc[2]
    

    示例用法

    with nested(a, b, c) as (x, y, z):
        # Perform operation
    

    等效于

    with a as x:
        with b as y:
            with c as z:
                # Perform operation
    

    (Python 2.5 的 contextlib 模块包含此上下文管理器的版本)

参考实现

此 PEP 最初在 2005 年 6 月 27 日 Guido 的 EuroPython 主题演讲中被接受。后来它再次被接受,并添加了the __context__方法。此 PEP 在 Python 2.5a1 的 Subversion 中被实现。__context__()方法在 Python 2.5b1 中被移除

致谢

许多人对 PEP 中的想法和概念做出了贡献,包括所有在PEP 340PEP 346的致谢中提到的那些人。

另外感谢(按无意义的顺序):Paul Moore、Phillip J. Eby、Greg Ewing、Jason Orendorff、Michael Hudson、Raymond Hettinger、Walter Dörwald、Aahz、Georg Brandl、Terry Reedy、A.M. Kuchling、Brett Cannon,以及所有参与了 python-dev 讨论的人。

参考


来源:https://github.com/python/peps/blob/main/peps/pep-0343.rst

最后修改:2023-10-11 12:05:51 GMT