【问题标题】:How to force deletion of a python object?如何强制删除python对象?
【发布时间】:2011-10-09 23:47:41
【问题描述】:

我很好奇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. 在上面的代码 sn-p 中,酒吧是在 del foogc.collect()... 上关闭的,还是两者都没有?如果您想更好地控制这些细节(例如,当对象未被引用时应该关闭栏)通常的实现方式是什么?
  3. __del__ 被调用时,是否保证__init__ 已经被调用了?如果__init__ 提出了怎么办?

【问题讨论】:

    标签: python constructor garbage-collection destructor reference-counting


    【解决方案1】:

    关闭资源的方法是上下文管理器,也就是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__

    【讨论】:

    • > 关闭资源的方法是上下文管理器,也就是 with 语句。优秀的提示。不知道with 可以用于以这种方式包含任何对象的范围。
    【解决方案2】:

    一般来说,为了确保无论如何都会发生某些事情,您使用

    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

    对于您的问题 #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 已关闭,因为该错误仅在从未打开的情况下发生。
    • @afg 你对 gc 的看法是错误的,当对象被删除时,请参阅我的回答。
    • @Jochen-Ritzel 知道了。你的例子很能说明问题。
    【解决方案3】:

    也许您正在寻找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>
    

    【讨论】:

      【解决方案4】:
      1. 添加一个exit handler 以关闭所有栏。
      2. __del__() 在 VM 仍在运行时对对象的引用数达到 0 时被调用。这可能是由 GC 引起的。
      3. 如果__init__() 引发异常,则认为对象不完整,不会调用__del__()

      【讨论】:

      • 附带说明:os._exit 允许完全绕过所有关闭处理函数。
      猜你喜欢
      • 2010-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-28
      • 1970-01-01
      • 2023-03-16
      • 2023-03-29
      • 2014-07-28
      相关资源
      最近更新 更多