我搜索过,我无法提出任何使用python的__enter__
/ __exit__
而不是__init__
(或__new__
?)/ {的正当理由{1}}。
我了解__del__
/ __enter__
旨在与__exit__
语句一起用作上下文管理器,with
语句很棒。但与此相对应的是,这些块中的任何代码仅在该上下文中执行 。通过使用这些而不是with
/ __init__
,我似乎正在与调用者创建一个他们必须使用__del__
的隐式契约,但是没有办法强制执行这样的合同,并且合同是只通过文档(或阅读代码)进行沟通。这似乎是一个坏主意。
我似乎在with
块内使用__init__
/ __del__
获得相同的效果。但是通过使用它们而不是上下文管理方法,我的对象在其他场景中也很有用。
任何人都可以提出一个令人信服的理由,为什么我曾想要使用上下文管理方法而不是构造函数/析构函数方法?
如果有更好的地方可以提出这样的问题,请告诉我,但似乎没有太多关于此的信息。
这个问题是基于一个糟糕的(但很可能是常见的)假设,因为我总是使用with
来实例化一个新对象,在这种情况下with
非常接近于与__init__/__del__
(除非您无法控制何时或何时执行__enter__/__exit__
,否则由垃圾收集决定,如果进程终止,则可能永远不会被调用)。但是如果你在__del__
语句中使用预先存在的对象,它们当然是完全不同的。
答案 0 :(得分:13)
您似乎错过了一些差异:
上下文管理器有机会为您正在执行的块提供新对象。一些上下文管理器只返回self
(就像文件对象一样),但是,作为示例,数据库连接对象可以返回绑定到当前事务的游标对象。
上下文管理器不仅会通知上下文结束,还会通知退出是由异常引起的。然后,它可以决定处理该事件或在退出期间以不同方式作出反应。再次使用数据库连接作为示例,基于存在异常,您可以提交或中止事务。
__del__
仅在删除<em>对象的所有引用时调用。这意味着如果你需要对它进行多次引用而你可能控制或不控制它的生命周期,你就不能依赖它。然而,精确定义了上下文管理器出口。
上下文管理器可以重用,并且可以保持状态。数据库连接再次;你创建它一次,然后一次又一次地使用它作为上下文管理器,它将保持该连接打开。每次都不需要为此创建新对象。
这对于线程锁很重要,例如;你有来保持状态,这样一次只有一个线程可以持有锁。您可以通过创建一个锁定对象来执行此操作,然后使用with lock:
,以便执行该部分的不同线程可以在进入该上下文之前等待。
__enter__
和__exit__
方法构成上下文管理器协议,如果您确实想要管理上下文,则应该只使用这些方法。上下文管理器的目标是简化常见的try...finally
和try...except
模式,而不是管理单个实例的生命周期。见PEP 343 – The "with" Statement:
这个PEP在Python语言中添加了一个新的“with”语句,以便能够分解try / finally语句的标准用法。
答案 1 :(得分:4)
del x
不直接致电x.__del__()
您无法控制何时调用.__del__
,或实际上whether it gets called at all。
因此,使用__init__
/ __del__
进行上下文管理并不可靠。
答案 2 :(得分:2)
通过使用这些而不是
__init__
/__del__
,我似乎与调用者创建了一个隐式契约,他们必须使用with
,但是没有办法强制执行这样的合同
你有任何一种合同。如果用户使用您的对象而没有意识到它需要在使用后进行清理,无论您如何实施清理,他们都会搞砸。他们可能会永远保留对您的对象的引用,例如,阻止__del__
运行。
如果您有一个需要特殊清理的对象,则需要明确这个要求。您需要为用户提供with
功能和明确的close
或类似方法,以便让用户控制何时进行清理。您无法在__del__
方法中隐藏清理要求。作为安全措施,您可能也希望实施__del__
,但不能使用__del__
代替 with
或明确close
}。
话虽如此,但Python并没有承诺__del__
将会运行。当对象的引用计数降为0时,标准实现将运行__del__
,但如果引用存活到脚本末尾,或者对象处于引用循环中,则可能不会发生这种情况。其他实现不使用引用计数,使__del__
更难以预测。