花下猫语:最近,我在看 Python 3.10 版本的更新内容时,发现有一个对于上下文管理器的小更新,而后,忽然发现上下文管理器的设计 PEP 居然还没人翻译过!于是,我断断续续花了两周工夫,终于把这篇 PEP 翻译进去了。如果你不理解什么是 PEP,能够先查看这篇《学习 Python,怎能不懂点 PEP 呢?》,如果你也对翻译 PEP 感兴趣,欢送退出 Github 上的 peps-cn 我的项目。
PEP 原文: https://www.python.org/dev/peps/pep-0343
PEP 题目: PEP 343 — The “with” Statement
PEP 作者: Guido van Rossum, Nick Coghlan
创立日期: 2005-05-13
合入版本: 2.5
译者:豌豆花下猫 @Python 猫公众号
PEP 翻译打算:https://github.com/chinesehuazhou/peps-cn
摘要
本 PEP 提议在 Python 中新增一种 ”with” 语句,能够取代惯例的 try/finally 语句。
在本 PEP 中,上下文管理器提供 \_\_enter\_\_() 和 \_\_exit\_\_() 办法,在进入和退出 with 语句体时,这俩办法别离会被调用。
作者的批注
本 PEP 最后由 Guido 以第一人称编写,随后由 Nick Coghlan 依据 python-dev 上的探讨,做出了更新补充。所有第一人称的内容都出自于 Guido 的原文。
Python 的 alpha 版本公布周期裸露了本 PEP 以及相干文档和实现 [14] 中的术语问题。直到 Python 2.5 的第一个 beta 版本公布时,本 PEP 才稳定下来。
是的,本文某些中央的动词时态是凌乱的。到当初为止,咱们曾经创作此 PEP 一年多了,所以,有些本来在将来的事件,当初曾经成为过来了:)
介绍
通过对 PEP-340 及其代替计划的大量探讨后,我决定撤销 PEP-340,并提出了 PEP-310 的一个小变种。通过更多的探讨后,我又增加了一种机制,能够应用 throw() 办法,在挂起的生成器中抛出异样,或者用一个 close() 办法抛出一个 GeneratorExitexception;这些想法最后是在 python-dev [2] 上提出的,并失去了广泛的认可。我还将关键字改为了“with”。
(Python 猫注:PEP-340 也是 Guido 写的,他最后用的关键字是“block”,起初改成了其它 PEP 提议的“with”。)
在本 PEP 被承受后,以下 PEP 因为重叠而被回绝:
- PEP-310,牢靠的获取 / 开释对。这是 with 语句的原始提案。
- PEP-319,Python 同步 / 异步代码块。通过提供适合的 with 语句控制器,本 PEP 能够涵盖它的应用场景:对于 ’synchronize’,咱们能够应用示例 1 中的 ”locking” 模板;对于 ’asynchronize’,咱们能够应用相似的 ”unlock” 模板。我认为不必要给代码块加上“匿名的”锁;事实上,应该尽可能地应用明确的互斥锁。
PEP-340 和 PEP-346 也与本 PEP 重叠,但当本 PEP 被提交时,它们就自行撤销了。
对于本 PEP 晚期版本的一些探讨,能够在 Python Wiki[3] 上查看。
动机与摘要
PEP-340(即匿名的 block 语句)蕴含了许多弱小的创意:应用生成器作为代码块模板、给生成器增加异样解决和终结,等等。除了投诉之外,它还被很多人所拥护,他们不喜爱它是一个(潜在的)循环构造。这意味着块语句中的 break 和 continue 能够中断或持续块语句,即便它本来被当作非循环的资源管理工具。
然而,直到我读了 Raymond Chen 对流量管制宏 [1] 的鞭挞时,PEP-340 才走入了末路。Raymond 令人信服地指出,在宏中藏有流程管制会让你的代码变得难以捉摸,我感觉他的论点不仅实用于 C,同样实用于 Python。我意识到,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\_\_() 办法因为 bug 导致抛异样怎么办?那么所有都完了——但这并不比其余状况更糟;异样的实质就是,它们可能产生在 任何中央,你只能承受这一点。即使你写的代码没有 bug,KeyboardInterrupt 异样依然会导致程序在任意两个虚拟机操作码之间退出。)
这个论点简直让我驳回了 PEP-310,然而,PEP-340 还有一个亮点让我不忍放弃:应用生成器作为某些抽象化行为的“模板”,例如获取及开释一个锁,或者关上及敞开一个文件,这是一种很弱小的想法,通过该 PEP 的例子就能看得出来。
受到 Phillip Eby 对 PEP-340 的反提议(counter-proposal)的启发,我尝试创立一个装璜器,将适合的生成器转换为具备必要的 \_\_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,而后 VAR 的 \_\_exit\_\_() 办法会在 BLOCK1 退出时被调用。然而这里,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
在其余的探讨中,人们真的很喜爱可能“看到”生成器中的异样,只管仅仅是为了记日志;生成器不容许产生(yield)其它的值,因为 with 语句不应该作为循环应用(引发不同的异样是勉强能够承受的)。
为了做到这点,我倡议为生成器提供一个新的 throw() 办法,该办法以通常的形式承受 1 到 3 个参数(类型、值、回溯),示意一个异样,并在生成器挂起的中央抛出。
一旦咱们有了这个,下一步就是增加另一个生成器办法 close(),它用一个非凡的异样(即 GeneratorExit)调用 throw(),能够令生成器退出。有了这个,在生成器被当作垃圾回收时,能够让程序主动调用 close()。
最初,咱们能够容许在 try-finally 语句中应用 yield 语句,因为咱们当初能够保障 finally 子句必然被执行。对于终结(finalization)的常见注意事项——过程可能会在没有终结任何对象的状况下忽然被终止,而这些对象可能会因程序的周期或内存透露而永远存活(在 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\_\_() 的调用约定如下。如果 finally 子句是通过 BLOCK 的失常实现或通过非部分 goto(即 BLOCK 中的 break、continue 或 return 语句)达到,则应用三个 None 参数调用 mgr.\_\_exit\_\_()。如果 finally 子句是通过 BLOCK 引发的异样达到,则应用异样的类型、值和回溯这三个参数调用 mgr.\_\_exit\_\_()。
重要:如果 mgr.\_\_exit\_\_() 返回“true”,则异样将被“吞灭”。也就是说,如果返回 ”true”,即使在 with 语句外部产生了异样,也会继续执行 with 语句之后的下一条语句。然而,如果 with 语句通过非部分 goto(break、continue 或 return)跳出,则这个非部分返回将被重置,不论 mgr.\_\_exit\_\_() 的返回值是什么。这个细节的动机是使 mgr.\_\_exit\_\_() 可能吞咽异样,而不使异样产生影响(因为默认的返回值 None 为 false,这会导致异样被从新 raise)。吞下异样的主要用途是使编写 @contextmanager 装璜器成为可能,这样被装璜的生成器中的 try/except 代码块的行为就如同生成器的主体在 with- 语句里内联开展了一样。
之所以将异样的细节传给 \_\_exit\_\_(),而不必 PEP -310 中不带参数的 \_\_exit\_\_(),起因是思考到上面例子 3 的 transactional()。该示例会依据是否产生异样,从而决定提交或回滚事务。咱们没有用一个 bool 标记辨别是否产生异样,而是传了残缺的异样信息,目标是能够记录异样日志。依赖于 sys.exc_info() 获取异样信息的提议被回绝了;因为 sys.exc_info() 有着非常复杂的语义,它返回的异样信息齐全有可能是很久之前就捕捉的。有人还提议增加一个布尔值,用于辨别是达到 BLOCK 结尾,还是非部分 goto。这因为过于简单和不必要而被回绝;对于数据库事务回滚,非部分 goto 应该被认为是失常的。
为了促成 Python 代码中上下文的链接作用,\_\_exit\_\_() 办法不应该持续 raise 传递给它的谬误。在这种状况下,\_\_exit\_\_() 办法的调用者应该负责解决 raise。
这样,如果调用者想晓得 \_\_exit\_\_() 是否调用失败(而不是在传出原始谬误之前就实现清理),它就能够本人判断。
如果 \_\_exit\_\_() 没有返回谬误,那么就能够将 \_\_exit\_\_() 办法自身解释为胜利(不论原始谬误是被流传还是克制)。
然而,如果 \_\_exit\_\_() 向其调用者流传了异样,这就意味着 \_\_exit\_\_() 自身曾经失败。因而,\_\_exit\_\_() 办法应该防止引发谬误,除非它们的确失败了。(容许原始谬误持续并不是失败。)
过渡打算
在 Python 2.5 中,新语法须要通过 future 引入:
from __future__ import with_statement
它会引入 ’with’ 和 ’as’ 关键字。如果没有导入,应用 ’with’ 或 ’as’ 作为标识符时,将导致报错。
在 Python 2.6 中,新语法总是失效的,’with’ 和 ’as’ 曾经是关键字。
生成器装璜器
随着 PEP-342 被驳回,咱们能够编写一个装璜器,令其应用只 yield 一次的生成器来管制 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 曾经敞开了)。
另一方面,这样的谬误很容易诊断;例如,当第二个 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 代码块中所建设的运行时环境的次要线索。
目前为止,with 语句体中的代码和 as 关键字前面的变量名(一个或多个)还没有非凡的术语。能够应用个别的术语“语句体”和“指标列表”,如果这些术语不清晰,能够应用“with”或“with statement”作为前缀。
思考到可能存在 decimal 模块的算术上下文这样的对象,因而术语“上下文”是有歧义的。如果想要更加具体的话,能够应用术语“上下文管理器”,示意上下文表达式所创立的具体对象;应用术语“运行时上下文”或者(最好是)” 运行时环境 ”,示意上下文管理器所做出的理论状态的变更。当简略地探讨 with 语句的用法时,歧义性无关紧要,因为上下文表达式齐全定义了对运行时环境所做的更改。当探讨 with 语句自身的机制以及如何理论实现上下文管理器时,这些术语的区别才是重要的。
缓存上下文管理器
许多上下文管理器(例如文件和基于生成器的上下文)都是一次性的对象。一旦 \_\_exit\_\_() 办法被调用,上下文管理器将不再可用(例如:文件曾经被敞开,或者底层生成器曾经实现执行)。
对于多线程代码,以及嵌套的 with 语句想要应用同一个上下文管理器,最简略的办法是给每个 with 语句一个新的管理器对象。并非偶合的是,规范库中所有反对重用的上下文管理器都来自 threading 模块——它们都被设计用来解决由线程和嵌套应用所产生的问题。
这意味着,为了保留带有特定初始化参数(为了用在多个 with 语句)的上下文管理器,通常须要将它存储在一个无参数的可调用对象,而后在每个语句的上下文表达式中调用,而不是间接把上下文管理器缓存起来。
如果此限度不实用,在受影响的上下文管理器的文档中,应该分明地指出这一点。
解决的问题
以下的问题经由 BDFL 的裁决而解决(并且在 python-dev 上没有重大的拥护意见)。
1、当底层的生成器 - 迭代器行为异样时,GeneratorContextManager 应该引发什么异样?上面援用的内容是 Guido 为本 PEP 及 PEP-342(见[8])中生成器的 close() 办法抉择 RuntimeError 的起因:“我不违心只是为了它而引入一个新的异样类,因为这不是我想让人们捕捉的异样:我想让它变成一个回溯(traceback),被程序员看到并且修复。因而,我认为它们都应该引发 RuntimeError。有一些引发 RuntimeError 的先例:Python 外围代码在检测到有限递归时,遇到未初始化的对象时(以及其它各种各样的状况)。”
2、如果在 with 语句所波及的类中没有相干的办法,则最好是抛出 AttributeError 而不是 TypeError。形象对象 C API 引发 TypeError 而不是 AttributeError,这只是历史的一个偶尔,而不是通过三思而行的设计决策[11]。
3、带有 \_\_enter\_\_ /\_\_exit\_\_办法的对象被称为“上下文管理器”,将生成器函数转化为上下文管理器工厂的是 contextlib.contextmanager 装璜器。在 2.5 版本公布期间,有人提议应用其它的叫法[16],但没有足够令人信服的理由。
回绝的选项
在长达几个月的工夫里,对于是否要克制异样(从而防止暗藏的流程管制),呈现了一场令人苦楚的拉锯战,最终,Guido 决定要克制异样[13]。
本 PEP 的另一个话题也引起了无休止的争执,即是否要提供一个 \_\_context\_\_() 办法,相似于可迭代对象的 \_\_iter\_\_() 办法 5[9]。源源不断的问题 10 在解释它是什么、为什么是那样、以及它是如何工作的,最终导致 Guido 齐全摈弃了这个货色[15](这很让人欢欣鼓舞!)
还有人提议间接应用 PEP-342 的生成器 API 来定义 with 语句[6],但这很快就不予考虑了,因为它会导致难以编写不基于生成器的上下文管理器。
例子
基于生成器的示例依赖于 PEP-342。另外,有些例子是不实用的,因为规范库中有现成的对象能够在 with 语句中间接应用,例如 threading.RLock。
例子中那些函数名所用的时态并不是随便的。过来时态(“-ed”)的函数指的是在 \_\_enter\_\_办法中执行,并在 \_\_exit\_\_办法中反执行的动作。进行时态(”-ing”)的函数指的是筹备在 \_\_exit\_\_办法中执行的动作。
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 给出了一个用例,它也有一个 release() 上下文,能长期开释先前取得的锁;这个用例跟前文的例子 4 很类似,只是替换了 acquire() 和 release() 的调用:
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 模块蕴含了这个上下文管理器的一个版本)
参考实现
在 2005 年 6 月 27 日的 EuroPython 会议上,Guido 首次驳回了这个 PEP。之后它增加了 \_\_context\_\_办法,并被再次驳回。此 PEP 在 Python 2.5 a1 子版本中实现,\_\_context\_\_() 办法在 Python 2.5b1 中被删除。
致谢
许多人对这个 PEP 中的想法和概念作出了奉献,包含在 PEP-340 和 PEP-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 探讨的人。
参考链接
[1] Raymond Chen’s article on hidden flow controlhttps://devblogs.microsoft.com/oldnewthing/20050106-00/?p=36783
[2] Guido suggests some generator changes that ended up in PEP 342https://mail.python.org/pipermail/python-dev/2005-May/053885.html
[3] Wiki discussion of PEP 343http://wiki.python.org/moin/WithStatement
[4] Early draft of some documentation for the with statementhttps://mail.python.org/pipermail/python-dev/2005-July/054658.html
[5] Proposal to add the with methodhttps://mail.python.org/pipermail/python-dev/2005-October/056947.html
[6] Proposal to use the PEP 342 enhanced generator API directlyhttps://mail.python.org/pipermail/python-dev/2005-October/056969.html
[7] Guido lets me (Nick Coghlan) talk him into a bad idea ;)https://mail.python.org/pipermail/python-dev/2005-October/057018.html
[8] Guido raises some exception handling questionshttps://mail.python.org/pipermail/python-dev/2005-June/054064.html
[9] Guido answers some questions about the context methodhttps://mail.python.org/pipermail/python-dev/2005-October/057520.html
[10] Guido answers more questions about the context methodhttps://mail.python.org/pipermail/python-dev/2005-October/057535.html
[11] Guido says AttributeError is fine for missing special methodshttps://mail.python.org/pipermail/python-dev/2005-October/057625.html
[12] Original PEP 342 implementation patchhttp://sourceforge.net/tracker/index.php?func=detail&aid=1223381&group_id=5470&atid=305470
[13] (1, 2) Guido restores the ability to suppress exceptionshttps://mail.python.org/pipermail/python-dev/2006-February/061909.html
[14] A simple question kickstarts a thorough review of PEP 343https://mail.python.org/pipermail/python-dev/2006-April/063859.html
[15] Guido kills the context() methodhttps://mail.python.org/pipermail/python-dev/2006-April/064632.html
[16] Proposal to use ‘context guard’ instead of ‘context manager’https://mail.python.org/pipermail/python-dev/2006-May/064676.html
版权
本文档已进入公共畛域。
源文档:https://github.com/python/peps/blob/master/pep-0343.txt