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

67 投票
4 回答
116315 浏览
提问于 2025-04-16 21:58

我对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实例的“酒吧”都会被关闭?
  2. 在上面的代码片段中,del foo时“酒吧”会被关闭,还是在gc.collect()时关闭,或者都不是?如果你想更精细地控制这些细节(例如,当对象不再被引用时“酒吧”应该关闭),通常该怎么实现?
  3. __del__被调用时,是否可以保证__init__已经被调用过?如果__init__抛出异常呢?

4 个回答

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>
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__ 中创建了。

编辑:修正了当引用计数降到零时删除发生的情况。

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__ 会立即被调用。垃圾回收(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__ 会被调用。

撰写回答