Python__Enter__/__Exit__vs__init__(或__new__)/__del__

问题描述

我已经搜索过了,但我找不出任何好的理由来使用python的__enter__/__exit__而不是__init__(或__new__?)/__del__

我知道__enter__/__exit__旨在与with语句一起用作上下文管理器,with语句非常好。但与此对应的是,这些块中的任何代码都只在该上下文中执行。通过使用这些而不是__init__/__del__,我似乎是在与调用者创建一个他们必须使用with的隐式约定,但没有办法强制执行这样的约定,并且该约定仅通过文档(或阅读代码)进行通信。这似乎不是个好主意。

with块中使用__init__/__del__似乎得到了相同的效果。但通过使用它们而不是上下文管理方法,我的对象在其他场景中也很有用。

那么有谁能提出一个令人信服的理由,为什么我会永远想要使用上下文管理方法而不是构造函数/析构函数方法?

如果有更好的地方问这样的问题,请让我知道,但似乎没有太多关于这方面的好信息。

跟进:

这个问题基于一个不好的(但很常见的)假设,因为我总是使用with实例化一个新对象,在这种情况下,__init__/__del__非常接近__enter__/__exit__的行为(除了您无法控制何时或是否将执行__del__,这取决于垃圾回收,如果进程首先终止,它可能永远不会被调用)。但是,如果您在with语句中使用预先存在的对象,它们当然会有很大的不同。


解决方案

您似乎忽略了几个差异:

  • 上下文管理器有机会只为您正在执行的块提供一个新对象。一些上下文管理器只返回selfhere(就像文件对象一样),但作为示例,数据库连接对象可以返回绑定到当前事务的游标对象。

  • 上下文管理器不仅会收到上下文结束的通知,还会收到退出是否由异常引起的通知。然后,它可以决定是处理该事件,还是在退出期间做出不同的反应。再次以数据库连接为例,根据存在的异常,您可以提交或中止事务。

  • __del__仅在删除对对象的所有引用时才调用。这意味着,如果您需要多个对它的引用,并且可能控制或不控制其生命周期,则不能依赖于调用它。但是,上下文管理器出口是精确定义的。

  • 上下文管理器可以重复使用,并且可以保持状态。数据库连接;您创建它一次,然后一次又一次地将其用作上下文管理器,它将使该连接保持打开状态。不需要每次都为此创建一个新对象。

    这对于线程锁很重要,例如,您必须保持状态,以便一次只能有一个线程持有锁。为此,您可以创建一个锁定对象,然后使用with lock:,这样执行该节的不同线程都可以在进入该上下文之前等待。

__enter____exit__方法形成了上下文管理器协议,只有当您真正想要管理上下文时才应该使用它们。上下文管理器的目标是简化常见的try...finallytry...except模式,而不是管理单个实例的生命周期。请参见PEP 343 – The "with" Statement:

此PEP向Python语言添加了一个新的"with"语句,以便能够排除try/Finally语句的标准用法。

相关文章