淺談__del__()方法的特殊用法

      網友投稿 1118 2025-03-31

      Python中有一些特殊方法,它們允許我們的類和Python更好地集成。在標準庫參考(Standard Library Reference)中,它們被稱為基本特殊方法,是與Python的其他特性無縫集成的基礎。

      __del__()方法有一個讓人費解的使用場景。

      這個方法的目的是在將一個對象從內存中清除之前,可以有機會做一些清理工作。如果使用上下文管理對象或者with語句來處理這種需求會更加清晰。對于Python的垃圾回收機制而言,創建一個上下文比使用__del__()更加容易預判。

      但是,如果一個Python對象包含了一些操作系統的資源,__del__()方法是把資源從程序中釋放的最后機會。例如,引用了一個打開的文件、安裝好的設備或者子進程的對象,如果我們將資源釋放作為__del__()方法的一部分實現,那么我們就可以保證這些資源最后會被釋放。

      很難預測什么時候__del__()方法會被調用。它并不總是在使用del語句刪除對象時被調用,當一個對象因為命名空間被移除而被刪除時,它也不一定被調用。Python文檔中用不穩定來描述__del__()方法的這種行為,并且提供了額外的關于異常處理的注釋:運行期的異常會被忽略,相對地,會使用sys.stderr打印一個警告。

      基于上面的這些原因,通常更傾向于使用上下文管理器,而不是實現__del__()。

      1. 引用計數和對象銷毀

      CPython的實現中,對象會包括一個引用計數器。當對象被賦值給一個變量時,這個計數器會遞增;當變量被刪除時,這個計數器會遞減。當引用計數器的值為0時,表示我們的程序不再需要這個對象,并且可以銷毀這個對象。對于簡單對象,當執行刪除對象的操作時會調用__del__()方法。

      對于包含循環引用的復雜對象,引用計數器有可能永遠也不會歸零,這樣就很難讓__del__()方法被調用。

      我們用下面的一個類來看看這個過程中到底發生了什么。

      class?Noisy:?  def?__del__(?self?):?    print(?"Removing?{0}".format(id(self))?)

      我們可以像下面這樣創建和刪除這個對象。

      >>>?x=?Noisy()?>>>del?x?Removing?4313946640

      我們先創建,然后刪除了Noisy對象,幾乎是立刻就看到了__del__()方法中輸出的消息。這也就是說,當變量x被刪除后,引用計數器正確地歸零了。一旦變量被刪除,就沒有任何地方引用Noisy實例,所以它也可以被清除。

      下面是淺復制中一種常見的情形。

      >>>?ln?=?[?Noisy(),?Noisy()?]?>>>?ln2=?ln[:]?>>>?del?ln

      Python沒有響應del語句。這說明這些Noisy對象的引用計數器還沒有歸零,肯定還有其他地方引用了它們,下面的代碼驗證了這一點。

      >>>?del?ln2?Removing?4313920336?Removing?4313920208

      ln2變量是ln列表的一個淺復制。有兩個列表引用了Noisy對象,所以在這兩個列表被刪除并且引用計數器歸零之前,Python不會銷毀這兩個Noisy對象。

      還有很多種創建淺復制的方法。下面是其中的一些。

      a?=?b?=?Noisy()?c?=?[?Noisy()?]?*?2

      這里的關鍵是,由于淺復制在Python中非常普遍,所以我們往往對存在的對象的引用感到非常困惑。

      2. 循環引用和垃圾回收

      下面是一種常見的循環引用的情形。一個父類包含一個子類的集合,同時集合中的每個子類實例又包含父類的引用。

      下面我們用這兩個類來看看循環引用。

      class?Parent:?  def?__init__(?self,?*children?):?    self.children=?list(children)?    for?child?in?self.children:?      child.parent=?self?  def?__del__(?self?):?    print(?"Removing?{__class__.__name__}?{id:d}".?format(?__class__=self.__class__,?id=id(self))?)?class?Child:?  def?__del__(?self?):?    print(?"Removing?{__class__.__name__}?{id:d}".?format(?__class__=self.__class__,?id=id(self))?)

      一個Parent的instance包括一個children的列表。

      每一個Child的實例都有一個指向Parent類的引用。當向Parent內部的集合中插入新的Child實例時,這個引用就會被創建。

      我們故意把這兩個類寫得比較復雜,所以下面讓我們看看當試圖刪除對象時,會發生什么。

      >>>>?p?=?Parent(?Child(),?Child()?)?>>>?id(p)?4313921808?>>>?del?p

      Parent和它的兩個初始Child實例都不能被刪除,因為它們之間互相引用。

      下面,我們創建一個沒有Child集合的Parent實例。

      >>>?p=?Parent()?>>>?id(p)?4313921744?>>>?del?p?Removing?Parent?4313921744

      和我們預期的一樣,這個Parent實例成功地被刪除了。

      由于互相之間有引用存在,因此我們不能從內存中刪除Parent實例和它包含的Child實例的集合。如果我們導入垃圾回收器的接口——gc,我們就可以回收和顯示這些不能被刪除的對象。

      下面的代碼中,我們使用了gc.collect()方法回收所有定義了__del__()方法但是無法被刪除的對象。

      >>>?import?gc?>>>?gc.collect()?174?>>>?gc.garbage?[<__main__.Parent?object?at?0x101213910>,?<__main__.Child?object?at?0x101213890>,?<__main__.Child?object?at?0x101213650>,?<__main__.Parent?object?at?0x101213850>,?<__main__.Child?object?at?0x1012130d0>,?<__main__.Child?object?at?0x101219a10>,?<__main__.Parent?object?at?0x101213250>,?<__main__.Child?object?at?0x101213090>,?<__main__.Child?object?at?0x101219810>,?<__main__.Parent?object?at?0x101213050>,?<__main__.Child?object?at?0x101213210>,?<__main__.Child?object?at?0x101219f90>,?<__main__.Parent?object?at?0x101213810>,?<__main__.Child?object?at?0x1012137d0>,?<__main__.Child?object?at?0x101213790>]

      可以看到,我們的Parent對象(例如,4313921808的ID = 0x101213910)在不可刪除的垃圾對象列表中很突出。為了讓引用計數器歸零,我們需要刪除所有Parent對象中的children列表,或者刪除所有Child實例中對Parent的引用。

      注意,即使把清理資源的代碼放在__del__()方法中,我們也沒辦法解決循環引用的問題。因為__del__()方法是在循環引用被解除并且引用計數器已經歸零之后被調用的。當有循環引用時,我們不能只是簡單地依賴于Python中計算引用數量的機制來清理內存中的無用對象。我們必須顯式地解除循環引用或者使用可以保證垃圾回收的weakref引用。

      3. 循環引用和weakref模塊

      如果我們需要循環引用,但是又希望將清理資源的代碼寫在__del__()中,這時候我們可以使用弱引用。循環引用的一個常見場景是互相引用:一個父類中包含了一個集合,集合中的每一個實例也包含了一個指向父類的引用。如果一個Player對象中包含多個Hand實例,那么在每一個Hand對象中都包括一個指向對應的Player類的引用可能會更方便。

      默認的對象間的引用可以被稱為強引用,但是,叫直接引用可能更好。Python的引用計數機制會直接使用它們,而且如果引用計數無法刪除這些對象的話,垃圾回收機器也能及時發現。它們是不可忽略的對象。

      對一個對象的強引用就是直接引用,下面是一個例子。

      當我們遇到如下語句。

      a=?B()

      變量a直接引用了B類的一個對象。此時B的引用計數至少是1,因為a變量包含了一個指向它的引用。

      想要找個一個弱引用相關的對象需要兩個步驟。一個弱引用會調用x.parent(),這個函數將弱引用作為一個可調用對象來查找它真正的父對象。這個過程讓引用計數器得以歸零,垃圾回收器可以回收引用的對象,但是不回收這個弱引用。

      weakref定義了一系列使用了弱引用而沒有使用強引用的集合。它讓我們可以創建一種特殊的字典類型,當這種字典的對象沒有用時,可以保證被垃圾回收。

      我們可以修改Parent和Child類,在Child指向Parent的引用中使用弱引用,這樣就可以簡單地保證無用對象會被銷毀。

      下面是修改后的類,它在Child指向Parent的引用中使用了弱引用。

      import?weakref?class?Parent2:?  def?__init__(?self,?*children?):?    self.children=?list(children)?    for?child?in?self.children:?      child.parent=?weakref.ref(self)?  def?__del__(?self?):?    print(?"Removing?{__class__.__name__}?{id:d}".format(?__class__=?self.__class__,?id=id(self))?)

      我們將Child中的parent引用改為一個weakref對象的引用。

      在Child類中,我們必須用上面說的兩步操作來定位parent對象。

      p?=?self.parent()?if?p?is?not?None:?  #?process?p,?the?Parent?instance?else:?  #?the?parent?instance?was?garbage?collected.

      我們可以顯式地確認引用的對象是否已經找到,因為有可能該引用已經變成虛引用。

      當我們使用這個新的Parent2類時,可以看到引用計數成功地歸零同時對象也被刪除了。

      >>>?p?=?Parent2(?Child(),?Child()?)?>>>?del?p?Removing?Parent2?4303253584?Removing?Child?4303256464?Removing?Child?4303043344

      淺談__del__()方法的特殊用法

      當一個weakref引用變成死引用時(因為引用被銷毀了),我們有3個可能的方案。

      重新創建引用對象,或重新從數據庫中加載。

      當垃圾回收器在低內存情況下錯誤地刪除了一些對象時,使用warnings模塊記錄調試信息。

      忽略這個問題。

      通常,weakref引用變成死引用是因為響應的對象已經被刪除了。例如,變量的作用域已經執行結束,一個沒有用的命名空間,應用程序正在關閉。對于這個原因,通常我們會采取第3種響應方法。因為試圖創建這個引用的對象時很可能馬上就會被刪除。

      4. __del__()方法和close()方法

      __del__()方法最常見的用途是確保文件被關閉。

      通常,包含文件操作的類都會有類似下面這樣的代碼。

      __del__?=?close

      這會保證__del__()方法同時也是close()方法。

      其他更復雜的情況最好使用上下文管理器。

      以上內容節選自《Python面向對象編程指南》

      Steven F. Lott的編程生涯開始于20世紀70年代,那時候計算機體積很大、昂貴并且非常少見。作為軟件工程師和架構師,他參與了100多個不同規模的項目研發。在使用Python解決業務問題方面,他已經有10多年的經驗了。

      Steven目前是自由職業者,居住在美國東海岸。他的技術博客是:http://slott-softwarearchitect.blogspot.com。

      譯者:

      張心韜——新加坡國立大學系統分析碩士,北京航空航天大學北海學院軟件工程學士。曾經就職于NEC(新加坡)和MobileOne(新加坡),目前投身金融領域,就職于GoSwiff(新加坡),擔任.NET軟件工程師,負責支付系統的研發工作。

      他在編程領域耕耘數年,涉獵甚廣,但自認“既非菜鳥,也非高人”。目前長期專注于.NET平臺,對Python也甚為喜愛。業余時間愛好甚廣,尤其喜歡學習中醫知識,對時間管理、經濟和歷史也略有涉獵。

      蘭亮:北京航空航天大學北海學院軟件工程學士,IT行業一線“碼農”,曾獲評“微軟2014年度MVP”和“微軟2015年度MVP”。曾一度混跡于飛信(中國)、NEC(新加坡)和MobileOne(新加坡),現就職于Keritos(新加坡),從事在線游戲研發工作。

      他雖然涉獵廣泛,但鐘愛開源,長期關注前沿技術,并且對算法、函數式編程、設計模式以及IT文化等有著濃厚興趣。工作之余,他喜歡在Coursera蹭課。作為一個熱愛生活的人,他在鉆研技術之余,還喜歡健身、旅行,立志成為一個陽光、向上的“碼農”。

      蘭亮個人網站 :?www.lan-liang.me

      博客:http://blog.csdn.net/lan_liang

      本文轉載自異步社區

      軟件開發 編程語言 python

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:如何把PPT做成多頁(ppt怎么制作多頁)
      下一篇:Excel中輕松創建帶有平滑線條的面積圖
      相關文章
      亚洲精品国产V片在线观看| 亚洲AV无码专区日韩| 亚洲第一男人天堂| 亚洲第一视频在线观看免费| 亚洲精华液一二三产区| 亚洲中文字幕无码久久综合网| 日本亚洲高清乱码中文在线观看| 亚洲第一页在线视频| 亚洲第一网站免费视频| 亚洲成人黄色在线观看| 久久亚洲AV无码精品色午夜 | 亚洲人成网www| 亚洲av成人无码久久精品| 亚洲Aⅴ无码专区在线观看q| 久久九九亚洲精品| 亚洲国产精品第一区二区| 97亚洲熟妇自偷自拍另类图片| 精品亚洲麻豆1区2区3区| 亚洲美女自拍视频| ww亚洲ww在线观看国产| 日韩亚洲国产综合高清| 亚洲hairy多毛pics大全| 噜噜噜亚洲色成人网站| 亚洲精品国精品久久99热| 国产精品亚洲w码日韩中文| 亚洲日韩国产精品第一页一区| 亚洲国产三级在线观看| 亚洲人成依人成综合网| 亚洲综合久久1区2区3区| 亚洲精品成人久久| 亚洲香蕉久久一区二区| 亚洲熟妇AV日韩熟妇在线| 国产精品亚洲а∨天堂2021| 亚洲国产日韩成人综合天堂| a级亚洲片精品久久久久久久| 亚洲乱码无码永久不卡在线| 亚洲Av熟妇高潮30p| 亚洲欧洲日产专区| 亚洲日韩AV一区二区三区四区| 婷婷亚洲综合五月天小说在线 | 亚洲欧洲精品视频在线观看|