我們知道,Python 通過調用 __init__() 方法構造當前類的實例化對象,而本節要學的 __del__() 方法,功能正好和 __init__() 相反,其用來銷毀實例化對象。
事實上在編寫程序時,如果之前創建的類實例化對象后續不再使用,最好在適當位置手動將其銷毀,釋放其占用的內存空間(整個過程稱為垃圾回收(簡稱GC))。
大多數情況下,Python 開發者不需要手動進行垃圾回收,因為 Python 有自動的垃圾回收機制(下面會講),能自動將不需要使用的實例對象進行銷毀。
無論是手動銷毀,還是 Python 自動幫我們銷毀,都會調用 __del__() 方法。舉個例子:
class CLanguage: def __init__(self): print("調用 __init__() 方法構造對象") def __del__(self): print("調用__del__() 銷毀對象,釋放其空間") clangs = CLanguage() del clangs
程序運行結果為:
調用 __init__() 方法構造對象 調用__del__() 銷毀對象,釋放其空間
但是,讀者千萬不要誤認為,只要為該實例對象調用 __del__() 方法,該對象所占用的內存空間就會被釋放。舉個例子:
class CLanguage: def __init__(self): print("調用 __init__() 方法構造對象") def __del__(self): print("調用__del__() 銷毀對象,釋放其空間") clangs = CLanguage() #添加一個引用clangs對象的實例對象 cl = clangs del clangs print("***********")
程序運行結果為:
調用 __init__() 方法構造對象 *********** 調用__del__() 銷毀對象,釋放其空間
可以看到,當程序中有其它變量(比如這里的 cl)引用該實例對象時,即便手動調用 __del__() 方法,該方法也不會立即執行。這和 Python 的垃圾回收機制的實現有關。注意,最后一行輸出信息,是程序執行即將結束時調用 __del__() 方法輸出的。
Python 采用自動引用計數(簡稱 ARC)的方式實現垃圾回收機制。該方法的核心思想是:每個 Python 對象都會配置一個計數器,初始 Python 實例對象的計數器值都為 0,如果有變量引用該實例對象,其計數器的值會加 1,依次類推;反之,每當一個變量取消對該實例對象的引用,計數器會減 1。如果一個 Python 對象的的計數器值為 0,則表明沒有變量引用該 Python 對象,即證明程序不再需要它,此時 Python 就會自動調用 __del__() 方法將其回收。
以上面程序中的 clangs 為例,實際上構建 clangs 實例對象的過程分為 2 步,先使用 CLanguage() 調用該類中的 __init__() 方法構造出一個該類的對象(將其稱為 C,計數器為 0),並立即用 clangs 這個變量作為所建實例對象的引用( C 的計數器值 + 1)。在此基礎上,又有一個 clang 變量引用 clangs(其實相當於引用 CLanguage(),此時 C 的計數器再 +1 ),這時如果調用
del clangs
語句,只會導致 C 的計數器減 1(值變為 1),因為 C 的計數器值不為 0,因此 C 不會被銷毀(不會執行 __del__() 方法)。
如果在上面程序結尾,添加如下語句:
del cl print("-----------")
則程序的執行結果為:
調用 __init__() 方法構造對象 *********** 調用__del__() 銷毀對象,釋放其空間 -----------
可以看到,當執行 del cl 語句時,其應用的對象實例對象 C 的計數器繼續 -1(變為 0),對於計數器為 0 的實例對象,Python 會自動將其視為垃圾進行回收。
需要額外說明的是,如果我們重寫子類的 __del__() 方法(父類為非 object 的類),則必須顯式調用父類的 __del__() 方法,這樣才能保證在回收子類對象時,其占用的資源(可能包含繼承自父類的部分資源)能被徹底釋放。為了說明這一點,這里舉一個反例:
class CLanguage: def __del__(self): print("調用父類 __del__() 方法") class cl(CLanguage): def __del__(self): print("調用子類 __del__() 方法") c = cl() del c
程序運行結果為:
調用子類 __del__() 方法