我很好奇python中__del__
的细节,什么时候应该用,为什么用,什么不应该用。我学到了一个艰难的方法,它并不像人们天真地期望从析构函数中得到的那样,因为它不是 __new__
/ __init__
的对立面。
class Foo(object):
def __init__(self):
self.bar = None
def open(self):
if self.bar != 'open':
print 'opening the bar'
self.bar = 'open'
def close(self):
if self.bar != 'closed':
print 'closing the bar'
self.bar = 'close'
def __del__(self):
self.close()
if __name__ == '__main__':
foo = Foo()
foo.open()
del foo
import gc
gc.collect()
我在文档中看到,不保证为解释器退出时仍然存在的对象调用 __del__()
方法。
如何保证解释器退出时存在的任何 Foo 实例都关闭栏?在上面的代码片段中,栏是否在 del foo 或 gc.collect() 上关闭...或两者都没有?如果您想更好地控制这些细节(例如,当对象未被引用时应该关闭栏)通常的实现方法是什么?当 __del__ 被调用时,是否保证 __init__ 已经被调用?如果 __init__ 提出了怎么办?
关闭资源的方法是上下文管理器,也就是 with
语句:
class Foo(object):
def __init__(self):
self.bar = None
def __enter__(self):
if self.bar != 'open':
print 'opening the bar'
self.bar = 'open'
return self # this is bound to the `as` part
def close(self):
if self.bar != 'closed':
print 'closing the bar'
self.bar = 'close'
def __exit__(self, *err):
self.close()
if __name__ == '__main__':
with Foo() as foo:
print foo, foo.bar
输出:
opening the bar
<__main__.Foo object at 0x17079d0> open
closing the bar
2) 当引用计数为 0 时,Python 的对象将被删除。在您的示例中,del foo
删除了最后一个引用,因此立即调用了 __del__
。 GC 与此无关。
class Foo(object):
def __del__(self):
print "deling", self
if __name__ == '__main__':
import gc
gc.disable() # no gc
f = Foo()
print "before"
del f # f gets deleted right away
print "after"
输出:
before
deling <__main__.Foo object at 0xc49690>
after
gc
与删除您和大多数其他对象无关。当简单的引用计数由于自引用或循环引用而不起作用时,它可以清理:
class Foo(object):
def __init__(self, other=None):
# make a circular reference
self.link = other
if other is not None:
other.link = self
def __del__(self):
print "deling", self
if __name__ == '__main__':
import gc
gc.disable()
f = Foo(Foo())
print "before"
del f # nothing gets deleted here
print "after"
gc.collect()
print gc.garbage # The GC knows the two Foos are garbage, but won't delete
# them because they have a __del__ method
print "after gc"
# break up the cycle and delete the reference from gc.garbage
del gc.garbage[0].link, gc.garbage[:]
print "done"
输出:
before
after
[<__main__.Foo object at 0x22ed8d0>, <__main__.Foo object at 0x22ed950>]
after gc
deling <__main__.Foo object at 0x22ed950>
deling <__main__.Foo object at 0x22ed8d0>
done
3)让我们看看:
class Foo(object):
def __init__(self):
raise Exception
def __del__(self):
print "deling", self
if __name__ == '__main__':
f = Foo()
给出:
Traceback (most recent call last):
File "asd.py", line 10, in <module>
f = Foo()
File "asd.py", line 4, in __init__
raise Exception
Exception
deling <__main__.Foo object at 0xa3a910>
对象是用 __new__
创建的,然后作为 self
传递给 __init__
。在 __init__
中出现异常后,对象通常没有名称(即 f =
部分未运行),因此它们的引用计数为 0。这意味着对象被正常删除并调用 __del__
。
通常,为了确保无论如何都会发生某些事情,您使用
from exceptions import NameError
try:
f = open(x)
except ErrorType as e:
pass # handle the error
finally:
try:
f.close()
except NameError: pass
无论 try
块中是否存在错误,以及在 except
块中发生的任何错误处理中是否存在错误,都会运行 finally
块。如果您不处理引发的异常,则在执行 finally
块后仍会引发异常。
确保文件关闭的一般方法是使用“上下文管理器”。
http://docs.python.org/reference/datamodel.html#context-managers
with open(x) as f:
# do stuff
这将自动关闭 f
。
对于您的问题 #2,bar
在引用计数达到零时立即关闭,如果没有其他引用,则在 del foo
上关闭。
对象不是由 __init__
创建的,它们是由 __new__
创建的。
http://docs.python.org/reference/datamodel.html#object.new
当您执行 foo = Foo()
时,实际上会发生两件事,首先是正在创建一个新对象,__new__
,然后它正在被初始化,__init__
。因此,在这两个步骤发生之前,您不可能调用 del foo
。但是,如果 __init__
中有错误,仍然会调用 __del__
,因为该对象实际上已经在 __new__
中创建。
编辑:如果引用计数减少到零,则在删除发生时更正。
try..except..finally
示例已损坏:如果 open()
引发异常 f
将被取消设置,并且 finally 将不起作用。
f
已关闭,因为该错误仅在从未打开的情况下发生。
也许您正在寻找 context manager?
>>> class Foo(object):
... def __init__(self):
... self.bar = None
... def __enter__(self):
... if self.bar != 'open':
... print 'opening the bar'
... self.bar = 'open'
... def __exit__(self, type_, value, traceback):
... if self.bar != 'closed':
... print 'closing the bar', type_, value, traceback
... self.bar = 'close'
...
>>>
>>> with Foo() as f:
... # oh no something crashes the program
... sys.exit(0)
...
opening the bar
closing the bar <type 'exceptions.SystemExit'> 0 <traceback object at 0xb7720cfc>
添加一个关闭所有柱的退出处理程序。 __del__() 在 VM 仍在运行时对对象的引用数达到 0 时被调用。这可能是由 GC 引起的。如果 __init__() 引发异常,则假定对象不完整并且不会调用 __del__()。
不定期副业成功案例分享
with
可以用于以这种方式包含任何对象的范围。