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
语句中使用预先存在的对象,它们当然会有很大的不同。
解决方案
您似乎忽略了几个差异:
上下文管理器有机会只为您正在执行的块提供一个新对象。一些上下文管理器只返回
self
here(就像文件对象一样),但作为示例,数据库连接对象可以返回绑定到当前事务的游标对象。上下文管理器不仅会收到上下文结束的通知,还会收到退出是否由异常引起的通知。然后,它可以决定是处理该事件,还是在退出期间做出不同的反应。再次以数据库连接为例,根据存在的异常,您可以提交或中止事务。
__del__
仅在删除对对象的所有引用时才调用。这意味着,如果您需要多个对它的引用,并且可能控制或不控制其生命周期,则不能依赖于调用它。但是,上下文管理器出口是精确定义的。上下文管理器可以重复使用,并且可以保持状态。数据库连接;您创建它一次,然后一次又一次地将其用作上下文管理器,它将使该连接保持打开状态。不需要每次都为此创建一个新对象。
这对于线程锁很重要,例如,您必须保持状态,以便一次只能有一个线程持有锁。为此,您可以创建一个锁定对象,然后使用
with lock:
,这样执行该节的不同线程都可以在进入该上下文之前等待。
__enter__
和__exit__
方法形成了上下文管理器协议,只有当您真正想要管理上下文时才应该使用它们。上下文管理器的目标是简化常见的try...finally
和try...except
模式,而不是管理单个实例的生命周期。请参见PEP 343 – The "with" Statement:
此PEP向Python语言添加了一个新的"with"语句,以便能够排除try/Finally语句的标准用法。
相关文章