2016-11-10 119 views
7

我已經搜查,我無法想出任何好的理由使用Python的__enter__/__exit__而不是__init__(或__new__?)/ __del__Python的__enter__/__exit__ VS __init__(或__new__)/ __del__

據我所知,__enter__/__exit__旨在與with語句一起用作上下文管理器,而with語句非常好。但與此相對應的是,那些塊中的任何代碼都是在該上下文中執行的只有。通過使用這些而不是__init__/__del__我似乎與呼叫者創建了一個隱式合同,他們必須使用with,但是沒有辦法執行這樣的合同,並且合同只通過文檔(或閱讀代碼)進行通信。這似乎是一個壞主意。

我似乎在with塊內使用__init__/__del__獲得相同的效果。但通過使用它們而不是上下文管理方法,我的對象在其他場景中也很有用。

那麼有人可以拿出一個令人信服的理由,爲什麼我會曾經想使用上下文管理方法而不是構造/析構函數?

如果有更好的地方提出這樣的問題,請讓我知道,但似乎沒有太多關於這方面的好消息。

追問:

這個問題是基於一個壞的(但很可能常見)的假設,因爲我總是用with實例化一個新的對象,在這種情況下__init__/__del__非常接近相同的行爲__enter__/__exit__(除了您無法控制何時或是否會執行__del__,取決於垃圾回收,並且如果該進程首先終止,則可能永遠不會調用該進程)。但是如果你在with聲明中使用預先存在的對象,它們當然是完全不同的。

+2

何時(甚至是)'__del__'被調用是不確定的。通過依賴'__del__'進行清理,可能會丟失數據。 – user2357112

+0

可能的重複:http://stackoverflow.com/a/6772907/674039 – wim

回答

7

有一些差異,你似乎已經錯過了:

  • 情境管理得到一個機會,只爲您正在執行的區塊提供了一個新的對象。有些上下文管理器在那裏返回self(就像文件對象一樣),但是數據庫連接對象可以返回與當前事務綁定的遊標對象。

  • 上下文管理器會收到通知上下文結束的通知,但是如果退出是由異常引起的。然後,它可以決定處理該事件,或者在退出時作出不同的反應。再次以數據庫連接爲例,基於存在異常,您可以提交或中止事務。

  • __del__僅在全部被調用對對象的引用被刪除。這意味着如果你需要多次引用它,你不能依靠它被調用,你可能會或可能不會控制它的生命週期。然而,上下文管理器出口是精確定義的。

  • 上下文管理器可以重複使用,它們可以保持狀態。數據庫連接再次;你創建一次,然後一次又一次地用它作爲上下文管理器,它會保持連接打開。無需每次都爲此創建一個新對象。

    這對線程鎖很重要,例如:你保持狀態,以便一次只有一個線程可以保持鎖。您可以通過創建一個鎖對象,然後使用with lock:來執行此操作,以便執行該部分的不同線程可以在進入該上下文之前等待。

__enter____exit__方法形成上下文管理協議,你應該只使用這些,如果你真的要管理的上下文。上下文管理器的目標是簡化常見的try...finallytry...except模式,而不是管理單個實例的生命週期。請參閱PEP 343 – The "with" Statement

此PEP爲Python語言添加了一個新的語句「with」,以便可以將try/finally語句的標準用法分解出來。

+0

你的前兩點是關於'with'語句,而不是進入和退出。我同意'with'很棒,但在這個問題中,我更關心的是編寫一個可以在'with'內使用的靈活對象的最佳方式。我得到'__del__'點。 – BobDoolittle

+2

@BobDoolittle:在沒有** with語句的情況下實現'__enter__'和'__exit__' **沒有什麼意義。 'with'聲明是我們首先使用這些方法的原因。 –

+0

當然有。這是服務器和客戶端之間的區別。來電和服務之間。我寫了一個對象。其他人可能會使用它。他們可能會在'with'塊中使用它,我無法強制使用。我應該以這種方式編寫我的對象,以便在任何情況下都能正確執行。像'File'對象 - 我可以在'with'語句中使用或不使用,並且它們都以任何方式工作。在'with'塊之外,我應該調用close()然而(但我懷疑'__del__'也這樣做)。 – BobDoolittle

2

del x不直接調用x.__del__()

你當.__del__被稱爲無法控制,其實還是whether it gets called at all

因此,使用__init__/__del__進行上下文管理並不可靠。

+0

我明白你關於'__del__'的觀點。但不是'__init__'。 '__init__'在上下文管理內部或外部應該是可靠的。在這一點上,我認爲編寫靈活對象的最好方法是將初始化代碼放在'__init__'而不是'__enter__'中,並且使__exit__和'__del__'做同樣的事情(同時保護反對重複執行)。事實上,我懷疑這正是File對象所做的。 – BobDoolittle

+1

@BobDoolittle:'__init__'用於初始化,所以如果你想做初始化,就在那裏做。 '__enter__'用於在輸入'with'語句時專門進行的工作;例如'__enter__'可能會鎖定一個鎖,而'__exit__'會解鎖它。 – user2357112

0

通過使用這些替代__init__/__del__我似乎建立與呼叫者的隱性契約,他們必須使用with,但沒有辦法執行這樣的合同

你有一個合同或者辦法。如果用戶使用你的對象而沒有意識到它需要在使用後進行清理,無論你如何實現清理,他們都會搞砸了。他們可能永遠保留對象的引用,例如,防止__del__運行。

如果您有一個需要特殊清理的對象,則需要明確指定此要求。您需要授予用戶with功能和明確的close或類似方法,以便讓用戶控制何時進行清理。您不能在__del__方法中隱藏清理要求。作爲安全措施,您可能也想實施__del__,但不能使用__del__代替with或明確的close


隨着中說,巨蟒不作任何承諾,相信__del__將運行,直到永遠。標準實現將在對象的引用計數下降到0時運行__del__,但如果引用存在於腳本末尾或對象處於引用循環中,則標準實現可能不會發生。其他實現不使用refcounting,使得__del__更不可預測。