7

我已经搜查,我无法想出任何好的理由使用Python的__enter__/__exit__而不是__init__(或__new__?)/ __del__Python的__enter__/__exit__ VS __init__(或__new__)/ __del__

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

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

那么有人可以拿出一个令人信服的理由,为什么我会曾经想使用上下文管理方法而不是构造/析构函数?

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

追问:

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

+2

何时(甚至是)'__del__'被调用是不确定的。通过依赖'__del__'进行清理,可能会丢失数据。 – user2357112

+0

可能的重复:http://stackoverflow.com/a/6772907/674039 – wim

回答

7

有一些差异,你似乎已经错过了:

  • 情境管理得到一个机会,只为您正在执行的区块提供了一个新的对象。有些上下文管理器在那里返回self(就像文件对象一样),但是数据库连接对象可以返回与当前事务绑定的游标对象。

  • 上下文管理器会收到通知上下文结束的通知,但是如果退出是由异常引起的。然后,它可以决定处理该事件,或者在退出时作出不同的反应。再次以数据库连接为例,基于存在异常,您可以提交或中止事务。

  • __del__仅在全部被调用对对象的引用被删除。这意味着如果你需要多次引用它,你不能依靠它被调用,你可能会或可能不会控制它的生命周期。然而,上下文管理器出口是精确定义的。

  • 上下文管理器可以重复使用,它们可以保持状态。数据库连接再次;你创建一次,然后一次又一次地用它作为上下文管理器,它会保持连接打开。无需每次都为此创建一个新对象。

    这对线程锁很重要,例如:你保持状态,以便一次只有一个线程可以保持锁。您可以通过创建一个锁对象,然后使用with lock:来执行此操作,以便执行该部分的不同线程可以在进入该上下文之前等待。

__enter____exit__方法形成上下文管理协议,你应该只使用这些,如果你真的要管理的上下文。上下文管理器的目标是简化常见的try...finallytry...except模式,而不是管理单个实例的生命周期。请参阅PEP 343 – The "with" Statement

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

+0

你的前两点是关于'with'语句,而不是进入和退出。我同意'with'很棒,但在这个问题中,我更关心的是编写一个可以在'with'内使用的灵活对象的最佳方式。我得到'__del__'点。 – BobDoolittle

+2

@BobDoolittle:在没有** with语句的情况下实现'__enter__'和'__exit__' **没有什么意义。 'with'声明是我们首先使用这些方法的原因。 –

+0

当然有。这是服务器和客户端之间的区别。来电和服务之间。我写了一个对象。其他人可能会使用它。他们可能会在'with'块中使用它,我无法强制使用。我应该以这种方式编写我的对象,以便在任何情况下都能正确执行。像'File'对象 - 我可以在'with'语句中使用或不使用,并且它们都以任何方式工作。在'with'块之外,我应该调用close()然而(但我怀疑'__del__'也这样做)。 – BobDoolittle

2

del x不直接调用x.__del__()

你当.__del__被称为无法控制,其实还是whether it gets called at all

因此,使用__init__/__del__进行上下文管理并不可靠。

+0

我明白你关于'__del__'的观点。但不是'__init__'。 '__init__'在上下文管理内部或外部应该是可靠的。在这一点上,我认为编写灵活对象的最好方法是将初始化代码放在'__init__'而不是'__enter__'中,并且使__exit__和'__del__'做同样的事情(同时保护反对重复执行)。事实上,我怀疑这正是File对象所做的。 – BobDoolittle

+1

@BobDoolittle:'__init__'用于初始化,所以如果你想做初始化,就在那里做。 '__enter__'用于在输入'with'语句时专门进行的工作;例如'__enter__'可能会锁定一个锁,而'__exit__'会解锁它。 – user2357112

0

通过使用这些替代__init__/__del__我似乎建立与呼叫者的隐性契约,他们必须使用with,但没有办法执行这样的合同

你有一个合同或者办法。如果用户使用你的对象而没有意识到它需要在使用后进行清理,无论你如何实现清理,他们都会搞砸了。他们可能永远保留对象的引用,例如,防止__del__运行。

如果您有一个需要特殊清理的对象,则需要明确指定此要求。您需要授予用户with功能和明确的close或类似方法,以便让用户控制何时进行清理。您不能在__del__方法中隐藏清理要求。作为安全措施,您可能也想实施__del__,但不能使用__del__代替with或明确的close


随着中说,巨蟒不作任何承诺,相信__del__将运行,直到永远。标准实现将在对象的引用计数下降到0时运行__del__,但如果引用存在于脚本末尾或对象处于引用循环中,则标准实现可能不会发生。其他实现不使用refcounting,使得__del__更不可预测。

相关问题