PEP 310 – 可靠的获取/释放对
- 作者:
- Michael Hudson <mwh at python.net>, Paul Moore <p.f.moore at gmail.com>
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建日期:
- 2002年12月18日
- Python 版本:
- 2.4
- 发布历史:
摘要
如果有一种打字量更少的方式来编写,那就太好了
the_lock.acquire()
try:
....
finally:
the_lock.release()
本 PEP 提出了一种语法(一个“with”块)和一个“小型”接口来概括上述内容。
声明
本 PEP 被拒绝,转而支持 PEP 343。
基本原理
Python 异常处理哲学的一个优点是它使“做错事”变得更困难(例如,未能检查某些系统调用的返回值)。目前,这不适用于资源清理。获取和释放资源(例如,锁)的当前语法是
the_lock.acquire()
try:
....
finally:
the_lock.release()
这种语法通过一个(可能很大的)代码块将获取和释放分开,这使得“一目了然”地确认代码正确管理资源变得困难。另一个常见错误是将“获取”调用编码在 try 块内,如果获取失败,这将错误地释放锁。
基本语法和语义
“with”语句的语法如下
'with' [ var '=' ] expr ':'
suite
该语句定义为等同于以下语句序列
var = expr
if hasattr(var, "__enter__"):
var.__enter__()
try:
suite
finally:
var.__exit__()
(__exit__ 方法的存在**不**像 __enter__ 那样被检查,以确保在 with 语句中使用不适当的对象会产生错误)。
如果变量被省略,则会在栈上分配一个无名对象。在这种情况下,该套件无法访问该无名对象。
可能的扩展
Python 开发者列表上已经讨论了基本语法的一些潜在扩展。这些扩展都没有包含在本 PEP 提出的解决方案中。在许多情况下,两方面的论点几乎同样有力。在这种情况下,PEP 总是选择简单性,仅仅因为在需要额外功能的地方,现有的 try 块是可用的。
多表达式
一个提议是允许在一个“with”语句中有多个表达式。__enter__ 方法将从左到右调用,而 __exit__ 方法将从右到左调用。这样做的好处是,当管理多个资源时,嵌套的“with”语句将导致代码向右侧边界漂移。解决这个问题的方法与任何其他深层嵌套问题相同——将部分代码提取到单独的函数中。此外,还需要解决如果其中一个 __exit__ 方法引发异常(是否应该调用其他 __exit__ 方法?)的问题。
异常处理
有人建议对协议进行扩展,以包含一个可选的 __except__ 处理器,该处理器在引发异常时被调用,并且可以处理或重新引发异常。目前尚不清楚此扩展的语义是否可以精确且易于理解。例如,如果定义了异常处理器,等效代码应该是 try ... except ... else 吗?如果没有,则为 try ... finally 吗?通常情况下,这如何在编译时确定?另一种方法是将代码定义为展开为一个 try ... except 块,该块位于一个 try ... finally 块内部。但这在实际生活中可能无法做到正确的事情。
识别出的唯一用于异常处理的用例是事务处理(在干净完成时提交,在异常时回滚)。这可能与传统的 try ... except ... else 块一样容易处理,因此本 PEP 不包含对异常处理程序的任何支持。
实现说明
在指定为等同于 with 语句的代码中存在一个潜在的竞态条件。例如,如果在 __enter__ 方法调用完成和 try 块开始之间引发 KeyboardInterrupt 异常,则 __exit__ 方法将不会被调用。这可能导致资源泄漏或死锁。[XXX Guido 曾表示他关心这种竞态条件,并打算编写一些 C 魔法来处理它们。“with”语句的实现应该复制这一点。]
未解决的问题
现有类(例如,类文件对象和锁)是否应该获得适当的 __enter__ 和 __exit__ 方法?赞成方的明显理由是便利性(不需要适配器)。反对的理由是,如果内置文件有这个而(比如说)StringIO 没有,那么在文件对象上使用“with”的代码就不能与 StringIO 对象重用。因此 __exit__ = close 成为“类文件对象”协议的一部分,用户定义的类可能需要支持。
__enter__ 钩子可能不是必需的——对于许多用例,需要一个适配器类,在这种情况下,__enter__ 钩子所做的工作可以很容易地在 __init__ 钩子中完成。
如果有一种显式控制对象生命周期的方法,__exit__ 钩子的功能可以由现有的 __del__ 钩子接管。与这种方法的支持者进行的一封电子邮件交流 [1] 使得其中一位作者更加确信这不是一个好主意……
有人建议 [2] 将“__exit__”方法命名为“close”,或者如果在没有找到 __exit__ 方法时应考虑“close”方法,以增加“with …”构造的“开箱即用实用性”。
“with …”块和生成器在概念上有一些相似之处,这导致了 for 循环可以实现 with 块功能的提议 [3]。虽然在某些层面上很巧妙,但我们认为 for 循环应该保持其作为循环的本质。
替代方案
IEXEC: Holger Krekel – 使用类似 XML 语法的通用方法(未找到 URL…)。
Holger 对“执行监视器”有更深远的设想,这些监视器会收到关于被监视块中控制流细节的信息。尽管这些设想很有趣,但它们可能会以深刻而微妙的方式改变语言,因此属于另一个 PEP。
任何 Smalltalk/Ruby 匿名块风格的扩展显然都包含了这个。
PEP 319 处于相同领域,但在 python-dev 上提出时未获得支持。
向后兼容性
本 PEP 提出了一个新关键字,因此需要玩 __future__ 游戏。
采纳成本
那些声称语言变得越来越庞大和复杂的人又有了新的抱怨点。这又是需要教授的东西。
为了使这个提议有用,标准库和其它代码中的许多类文件和类锁类将不得不具备
__exit__ = close
或类似的功能。
不采纳成本
编写正确的代码仍然比编写不正确的代码需要更多的精力。
参考资料
这里可以提及各种 python-list 和 python-dev 讨论。
版权
本文档已置于公共领域。
来源: https://github.com/python/peps/blob/main/peps/pep-0310.rst
最后修改: 2025-02-01 08:59:27 GMT