2016-02-18 34 views
8

我閱讀不同的方式來清理對象在Python中,我已經對這些問題跌跌撞撞(12)基本上說,清理使用__del__()是不可靠的,下面的代碼應避免:依靠__del __()進行python清理不可靠嗎?

def __init__(self): 
    rc.open() 

def __del__(self): 
    rc.close() 

問題是,我正在使用此代碼,並且我無法再現上述問題中引用的任何問題。據我所知,我不能替代with聲明,因爲我爲閉源軟件提供了一個Python模塊(testIDEA,任何人?)該軟件將創建特定類的實例並處理它們,這些實例必須準備好在兩者之間提供服務。我所看到的__del__()的唯一替代方法是根據需要手動調用open()close(),我認爲這會非常容易出錯。

我明白,當我關閉解釋器時,不能保證我的對象將被正確銷燬(並且它並不會影響我很多,甚至Python作者認爲它是確定的)。除此之外,我是否在使用__del__()進行清理? PS:我最初考慮發佈關於帶有無法複製的bug的問題的meta,但意識到我不會獲得我需要的技術細節。

+0

當解釋器關閉時,'__exit__'甚至可以保證執行...? –

+0

另外:我發現[此鏈接](http://eli.thegreenplace.net/2009/06/12/safely-using-destructors-in-python)對此主題非常有幫助。 –

+0

@RickTeachey你錯誤了'__del__'作爲'__exit__',還是你建議我在我的代碼中使用'__exit__'?另外,我可以在結束時容忍問題。 –

回答

3

您在垃圾回收的語言終結觀察典型問題。 Java擁有它,C#擁有它,並且它們都提供了一個基於範圍的清理方法,如Python with關鍵字來處理它。

主要問題是,垃圾收集器負責清理和銷燬對象。在C++中,當對象超出範圍時會被銷燬,所以您可以使用RAII並具有定義良好的語義。在Python中,只要GC喜歡,對象就會超出範圍並繼續生存。根據您的Python實現,這可能會有所不同。CPython的基於refcounting的GC是非常良性的(所以你很少看到問題),而PyPy,IronPython和Jython可能會讓對象長時間保持活躍狀態​​。

例如:

def bad_code(filename): 
    return open(filename, 'r').read() 

for i in xrange(10000): 
    bad_code('some_file.txt') 

bad_code泄漏的文件句柄。在CPython中並不重要。該refcount下降到零,它被立即刪除。在PyPy或IronPython中,你可能會得到IOErrors或類似的問題,因爲你用盡了所有可用的文件描述符(在Unix上達到ulimit或在Windows上達到509個句柄)。

如果需要保證清理,最好使用基於範圍的上下文管理器清理和with。你確切知道什麼時候你的對象將被最終確定。但有時候你不能輕易地強制執行這種範圍清理。這就是當你可能使用__del__,atexit或類似的結構盡最大努力清理。它不可靠,但總比沒有好。

您可以通過顯式清理或強制顯式範圍來減輕用戶負擔,也可以通過__del__來冒險,並且時不時地看到一些奇怪的現象(尤其是解釋器關閉)。

+0

謝謝。我實際上依賴的不僅僅是我的代碼中的盡力而爲的清理。你能否澄清一下CPython在你的例子中保證了什麼?最多隻有一個打開的句柄?最多10?還有別的嗎? –

+1

CPython使用引用計數系統來處理清理。所以在上面的例子中,句柄超出了範圍,這減少了refcount並被立即清理,因此在函數運行時只打開一個句柄。 – schlenk

+0

對我來說這夠好。我可以放棄使用特定Python解釋器來運行我的代碼的要求。 –

2

使用__del__來運行代碼有幾個問題。

其中之一,它只適用於如果你積極跟蹤引用,即使如此,也不能保證它會立即運行,除非你手動啓動整個代碼的垃圾收集。我不知道你的情況,但自動垃圾收集在準確地記錄參考資料方面已經非常糟糕。即使你在代碼中超級勤奮,你也依賴於其他用戶,這些用戶在引用計數時使用你的代碼時一樣勤快。

二,有很多__del__永遠不會運行的情況。在對象被初始化和創建時是否有異常?口譯員是否退出?有沒有循環引用?是的,在這裏可能會出現很多錯誤,並且很少有乾淨而一貫地處理它的方法。

三,即使它運行,它也不會引發異常,所以你不能像處理其他代碼一樣處理異常。幾乎不可能保證來自各種對象的方法將以任何特定的順序運行。因此,對於析構函數最常見的用例 - 清理和刪除一堆對象 - 是毫無意義的,不太可能按計劃進行。

如果你真的想代碼運行,有更好的機制 - 上下文管理器,信號/插槽,事件等

+0

您能否詳細說明__init__中的異常?我應該抓住並重新拋出嗎?我知道'有'是要走的路,但我必須爲封閉的來源提供對象,所以我的選項似乎有限 –

+0

如果您正在創建或執行'__init__'中的操作,並且在'__init__',它不會完成,並且對該類的引用永遠不會被創建,所以'__del__'將永遠不會被調用來拆除或清除在錯誤發生之前創建的任何內容。你必須提供什麼對象的規格?你能指點我的封閉源代碼庫的文檔嗎? –

+0

這裏有[這個頁面](http://www.isystem.com/downloads/testIDEA/help/),如果你想查看一下它有一些信息。相關部分是任務 - >編寫腳本擴展。 –