[英]What's the use of the __del__() method in Python?
來自Python 文檔:
不能保證在解釋器退出時為仍然存在的對象調用
__del__()
方法。
據我了解,也無法保證在解釋器退出之前對象停止存在,因為由垃圾收集器決定是否以及何時刪除對象。
那么使用這種方法有什么意義呢? 您可以在其中編寫清理代碼,但不能保證它會被執行。
我知道你可以使用try
- finally
或with
子句來解決這個問題,但我仍然想知道__del__()
方法的有意義的用例是什么。
它可用於處理由對象管理的資源: https ://github.com/python/cpython/blob/master/Lib/zipfile.py#L1805
如文檔字符串中所述,這是一種最后的手段,因為只有在 gc 運行時才關閉對象。
正如您在問題中所說,首選方法是通過直接調用.close()
或使用with Zipfile() as z:
的上下文管理器來調用close
自己:
在閱讀了所有這些答案(其中沒有一個能夠令人滿意地回答我所有的問題/疑問)並重新閱讀 Python 文檔之后,我得出了自己的結論。 這是我對此事的想法的總結。
您從__del__
方法文檔中引用的段落說:
不能保證在解釋器退出時為仍然存在的對象調用
__del__()
方法。
但是,不僅不能保證在解釋器退出期間為被銷毀的對象調用__del__()
,甚至不能保證對象被垃圾回收,即使在正常執行期間——來自 Python 語言的“數據模型”部分參考:
對象永遠不會被顯式銷毀; 但是,當它們變得無法訪問時,它們可能會被垃圾收集。 允許實現推遲垃圾收集或完全忽略它——只要沒有收集仍然可訪問的對象,垃圾收集的實現方式就是實現質量的問題。
因此,回答您的問題:
那么使用這種方法有什么意義呢? 您可以在其中編寫清理代碼,但不能保證它會被執行。
從與實現無關的角度來看, __del__
方法是否有任何用途,作為可以依賴的代碼的基本組件? 不,根本沒有。 從這個角度來看,它基本上是無用的。
但是,從實際的角度來看,正如其他答案所指出的那樣,您可以使用__del__
作為最后的機制來(嘗試)確保在對象被銷毀之前執行任何必要的清理,例如釋放資源,如果用戶忘記顯式調用close
方法。 這與其說是故障安全,不如說是“即使不能保證工作,添加額外的安全機制也沒有什么壞處”——事實上,大多數Python 實現大部分時間都會捕捉到這一點。 但這沒有什么可依賴的。
話雖如此,如果您知道您的程序將在一組特定的 Python 實現上運行,那么您可以依賴垃圾收集的實現細節——例如,如果您使用 CPython,您可以“依賴”以下事實:在正常執行期間(即在解釋器退出之外),如果非循環引用對象的引用計數達到零,它將被垃圾收集並調用其__del__
方法,正如其他答案所指出的那樣。 從與上述相同的小節:
CPython 實現細節: CPython 目前使用引用計數方案,對循環鏈接的垃圾進行(可選)延遲檢測,一旦它們變得不可訪問,它就會收集大多數對象,但不能保證收集包含循環引用的垃圾。
但是,這確實很不穩定,並且不能真正依賴,因為如前所述,它只保證不屬於循環引用圖的對象。 還:
其他實現的行為不同,CPython 可能會改變。 當對象變得無法訪問時,不要依賴於立即完成對象(因此您應該始終明確關閉文件)。
從純粹主義者的角度來看, __del__
方法完全沒用。 從稍微不那么純粹的角度來看,它仍然幾乎沒有用。 從實際的角度來看,它可能作為代碼的補充——但不是必不可少的——有用。
它基本上用於強制調用一個方法,一旦該對象的所有活動完成,就應該調用該方法,例如
def __del__(self):
self.my_func()
現在您確定my_func
將被稱為對象的所有工作完成。
運行這個程序,你就會知道發生了什么
class Employee:
def __init__(self, name):
self.name = name
print('Employee created.')
def get_name(self):
return self.name
def close(self):
print("Object closed")
# destructor
def __del__(self):
self.close()
obj = Employee('John')
print(obj.get_name())
# lets try deleting the object!
obj.__del__() # you don't need to run this
print("Program ends")
print(obj.get_name())
輸出
> Employee created.
> John
> Object closed
> Program ends
> John
> Object closed
你說:
不能保證在解釋器退出時為仍然存在的對象調用del () 方法。
這是非常正確的,但是在很多情況下,對象被創建,然后對這些對象的引用要么被設置為None
被顯式地“銷毀”,要么超出范圍。 這些對象,無論是在創建時還是在執行過程中,都會分配資源。 當用戶完成對象時,他應該調用close
或cleanup
方法來釋放這些資源。 但是最好有一個析構方法,即__del__
方法,當沒有更多對可以檢查該清理方法是否已調用的對象的引用時調用該方法,如果沒有調用清理本身。 在__del__
可能不會在退出時被調用的情況下,此時回收資源可能不是太重要,因為程序無論如何都會被終止(當然,在cleanup
或close
的情況下,不僅僅是回收資源,還有執行必要的終止功能,例如關閉文件,然后依賴__del__
在退出時調用確實會出現問題)。
關鍵是,在 CPython 等引用計數實現中,您可以依賴__del__
在對對象的最后一個引用被銷毀時調用:
import sys
class A:
def __init__(self, x):
self.x = x
def __del__(self):
print(f'A x={self.x} being destructed.')
a1 = A(1)
a2 = A(2)
a1 = None
# a1 is now destroyed
input('A(1) should have been destroyed by now ...')
a_list = [a2]
a_list.append(A(3))
a_list = None # A(3) should now be destroyed
input('A(3) should have been destroyed by now ...')
a4 = A(4)
sys.exit(0) # a2 and a4 may or may not be garbage collected
印刷:
A x=1 being destructed.
A(1) should have been destroyed by now ...
A x=3 being destructed.
A(3) should have been destroyed by now ...
A x=2 being destructed.
A x=4 being destructed.
除了對象a2
和a4
可能的例外,類A
的所有其他實例都將被“破壞”,即銷毀。
例如,實際用法是調用函數bar
來創建B
的實例,然后再創建A
的實例。 當函數bar
返回對B
的引用並因此對A
的引用被隱式銷毀時,如果尚未調用A
實例上的close
方法,則會自動進行清理:
class A:
def __init__(self, x):
self.x = x
self.cleanup_done = False
def close(self):
print(f'A x={self.x} being cleaned up.')
self.cleanup_done = True
def __del__(self):
if not self.cleanup_done:
self.close()
class B:
def __init__(self, x):
self.a = A(x)
def foo(self):
print("I am doing some work")
def bar():
b = B(9)
b.foo()
def other_function():
pass
if __name__ == '__main__':
bar()
other_function()
要讓B
實例顯式調用A
實例的close
方法,它必須實現自己的close
方法,然后將其委托給A
實例的close
方法。 但是,為此目的使用__del__
方法是沒有意義的。 因為如果這樣可行,那么A
實例自己的__del__
方法就足以進行清理。
當對象被銷毀時,會調用析構函數。 在 Python 中,C++ 中不需要析構函數,因為 Python 有一個自動處理內存管理的垃圾收集器。
__del__()
方法在 Python 中被稱為析構函數。 當對對象的所有引用都被刪除時調用它,即當一個對象被垃圾回收時。
析構函數聲明的語法:
def __del__(self):
# body of destructor
注意:當對象失去引用或程序結束時,對對象的引用也會被刪除。
示例 1:這是析構函數的簡單示例。 通過使用 del 關鍵字,我們刪除了對象 'obj' 的所有引用,因此自動調用了析構函數。
# Python program to illustrate destructor
class Employee:
# Initializing
def __init__(self):
print('Employee created.')
# Deleting (Calling destructor)
def __del__(self):
print('Destructor called, Employee deleted.')
obj = Employee()
del obj
#Output
#Employee created.
#Destructor called, Employee deleted.
注意:析構函數是在程序結束或對象的所有引用都被刪除時調用的,即引用計數變為零時調用,而不是在對象超出范圍時調用。
例 2:這個例子給出了上面提到的注釋的解釋。 在這里,請注意析構函數是在打印“程序結束...”之后調用的。
# Python program to illustrate destructor
class Employee:
# Initializing
def __init__(self):
print('Employee created')
# Calling destructor
def __del__(self):
print("Destructor called")
def Create_obj():
print('Making Object...')
obj = Employee()
print('function end...')
return obj
print('Calling Create_obj() function...')
obj = Create_obj()
print('Program End...')
#Output:
#Calling Create_obj() function...
#Making Object...
示例 3:現在,考慮以下示例:
# Python program to illustrate destructor
class A:
def __init__(self, bb):
self.b = bb
class B:
def __init__(self):
self.a = A(self)
def __del__(self):
print("die")
def fun():
b = B()
fun()
#Output:
#die
在這個例子中,當函數 fun() 被調用時,它創建了一個類 B 的實例,該實例將自身傳遞給類 A,然后設置對類 B 的引用並產生循環引用。
通常,用於檢測這些類型的循環引用的 Python 垃圾收集器會將其刪除,但在此示例中,使用自定義析構函數將此項標記為“不可回收”。 簡單地說,它不知道銷毀對象的順序,所以它離開了它們。 因此,如果您的實例涉及循環引用,只要應用程序運行,它們就會一直存在於內存中。
來源: Python 中的析構函數
我最近發現了一個需要實現__del__
的示例:
正如其他答案中提到的那樣,在執行程序期間根本無法保證在哪里甚至是否會調用__del__
。 但是,如果你翻轉這個語句,這也意味着它可能會在某個時候被調用。
就我而言,我有一個依賴於初始化期間創建的臨時文件的類,例如:
import tempfile
class A:
def __init__(self):
self.temp_file = tempfile.NamedTemporaryFile()
但是,我也碰巧依賴可能刪除該文件的外部程序(例如,如果其內容無效)。 在這種情況下,如果這個臨時文件與我的 A 實例一起被垃圾收集,則會引發異常,因為該文件不再存在。
所以解決方案是嘗試在__del__
方法中關閉文件並在那里捕獲異常(如果有):
def __del__(self):
try:
self.temp_file.close()
except FileNotFoundError:
# Do something here
pass
因此,通過在這種情況下實現__del__
,我可以指定應該如何處理資源以及如果引發異常應該做什么。
__del()__
像析構函數一樣工作,有時它會通過垃圾收集器自動調用(我說有時並不總是,因為你永遠不知道它是否會運行以及何時運行),所以使用起來有點無用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.