55

我很好奇python中的__del__的細節,何時以及爲什麼它應該被使用以及它不應該被用於什麼。我已經學會了艱難的方式,它不像真正想從一個析構者那裏得到的那樣,因爲它不是__new__/__init__的反義詞。如何強制刪除python對象?

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 foogc.collect()上關閉或者兩者都不關閉?如果你想更好地控制這些細節(例如當對象未被引用時該欄應該被關閉),那麼通常的方式是什麼?
  3. __del__被調用是否保證__init__已被調用?那麼如果__init__上升呢?

回答

64

的方式來關閉資源方面的經理,又名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__

+0

>關閉資源的方式是上下文管理器,也就是with語句。 優秀的提示。不知道'with'可以用來以這種方式包含任何對象的範圍。 –

+3

這個答案很有啓發性。感謝您的明確解釋! –

2
  1. 添加exit handler是關閉所有的酒吧。
  2. __del__()當虛擬機仍在運行時,當對象的引用數量達到0時被調用。這可能是由GC引起的。
  3. 如果__init__()引發異常,則假定該對象不完整,並且__del__()將不會被調用。
+2

附註:os._exit允許完全繞過所有關閉處理函數。 – cwallenpoole

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

對於您的問題#2,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

+0

謝謝,修復。但是,它仍然會確保'f'被關閉,因爲錯誤只發生在從未打開過的情況下。 – agf

+2

@afg您錯誤地瞭解了gc以及何時刪除了對象,請參閱我的答案。 –

5

也許您在尋找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>