如何强制删除一个Python对象?

67

我对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__()方法。

  1. 如何保证解释器退出时任何现有的Foo实例都已关闭bar
  2. 在上面的代码片段中,del foogc.collect()会关闭bar吗?如果你想更细致地控制这些细节(例如,在对象未被引用时应关闭bar),通常的实现方式是什么?
  3. 当调用__del__时,是否保证已经调用了__init__?如果__init__抛出异常会怎样?
4个回答

74
关闭资源的方法是使用上下文管理器,也称为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)当Python对象的引用计数为0时,它们将被删除。在您的示例中,del foo 删除了最后一个引用,因此立即调用了__del__。垃圾收集器与此无关。

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
< p> 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__


关闭资源的方法是上下文管理器,也就是使用with语句。 非常好的提示。以前不知道with可以用来为任何对象限定作用域。 - Lelouch Lamperouge

8
通常,为确保无论如何都发生某事,您可以使用
from exceptions import NameError

try:
    f = open(x)
except ErrorType as e:
    pass # handle the error
finally:
    try:
        f.close()
    except NameError: pass
finally 块将会在 try 块中出现错误或任何在 except 块中的错误处理中执行,即使你没有处理引发的异常, finally 块也将在其执行后继续引发该异常。
确保文件已关闭的一般方法是使用 "上下文管理器"。 http://docs.python.org/reference/datamodel.html#context-managers
with open(x) as f:
    # do stuff

这将自动关闭 f

对于您的第二个问题,当引用计数达到零时,bar会立即关闭,因此在没有其他引用的情况下,del foo会关闭它。

对象不是由__init__创建的,而是由__new__创建的。

http://docs.python.org/reference/datamodel.html#object.new

当您执行foo = Foo()时,实际上会发生两件事,首先会创建一个新对象,__new__,然后进行初始化,__init__。因此,在这两个步骤都完成之前,您不可能调用del foo。但是,如果__init__中存在错误,则仍将调用__del__,因为对象实际上已经在__new__中创建。

编辑:更正了当引用计数减少到零时删除发生的时间。


1
你的 try..except..finally 示例有问题:如果 open() 抛出异常,f 将未设置,finally 将不起作用。 - Duncan
谢谢,问题已解决。然而,这仍然可以确保 f 被关闭,因为错误只会在它从未打开的情况下发生。 - agf
2
@afg,你关于垃圾回收和对象删除的观点是错误的,请看我的回答。 - Jochen Ritzel
@Jochen-Ritzel 我明白了。您的示例非常具有说明性。 - agf

5
也许您正在寻找一个上下文管理器?点击此处了解更多。
>>> 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>

2
  1. 添加一个退出处理器,关闭所有酒吧。
  2. 当虚拟机仍在运行时,对象引用计数为0时会调用__del__()。这可能是由于垃圾回收导致的。
  3. 如果__init__()引发异常,则假定该对象不完整,__del__()将不会被调用。

2
顺便提一下:os._exit允许完全绕过所有关闭处理函数。 - cwallenpoole

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接