簡體   English   中英

Python 中的 __del__() 方法有什么用?

[英]What's the use of the __del__() method in Python?

來自Python 文檔

不能保證在解釋器退出時為仍然存在的對象調用__del__()方法。

據我了解,也無法保證在解釋器退出之前對象停止存在,因為由垃圾收集器決定是否以及何時刪除對象。

那么使用這種方法有什么意義呢? 您可以在其中編寫清理代碼,但不能保證它會被執行。

我知道你可以使用try - finallywith子句來解決這個問題,但我仍然想知道__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被顯式地“銷毀”,要么超出范圍。 這些對象,無論是在創建時還是在執行過程中,都會分配資源。 當用戶完成對象時,他應該調用closecleanup方法來釋放這些資源。 但是最好有一個析構方法,即__del__方法,當沒有更多對可以檢查該清理方法是否已調用的對象的引用時調用該方法,如果沒有調用清理本身。 __del__可能不會在退出時被調用的情況下,此時回收資源可能不是太重要,因為程序無論如何都會被終止(當然,在cleanupclose的情況下,不僅僅是回收資源,還有執行必要的終止功能,例如關閉文件,然后依賴__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.

除了對象a2a4可能的例外,類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()

Python 演示

要讓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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM