天天看點

Python變量及垃圾回收機制

注釋是代碼之母,學習任何一門程式設計語言之前都應該先學注釋。

注釋就是對代碼的解釋說明,注釋的内容不會被當做代碼運作,隻起到提示作用

增強代碼的可讀性

代碼注釋分為單行和多行注釋

方式1:單行注釋使用"#"号,可以跟在代碼的正上方或者正後方

方式2:多行注釋可以用三對引号(單雙引号都可以)

方式3:PyCharm注釋快捷鍵—— Ctrl + Alt + L

Python變量及垃圾回收機制

不用全部加注釋,隻需要為自己覺得重要或不好了解的部分加注釋即可

注釋可以用中文或英文,但不要用拼音

“#”号與注釋文本之間一定要有一個空格

如果單行注釋跟在了一行代碼的後面,需要先空兩個空格,再寫注釋

變量就是可以變化的量,量指的是事物的狀态,比如人的年齡、性别,遊戲角色的等級、金錢等等

為了讓計算機能夠像人一樣去記憶事物的某種狀态,并且狀态是可以發生變化的

程式執行的本質就是一系列狀态的變化,變是程式執行的直接展現,所有我們需要有一種機制能夠反映或者說是儲存下來程式執行時的狀态,以及狀态的變化。

變量的定義與使用

定義變量示範如下

Python變量及垃圾回收機制

解釋器執行到變量定義的代碼時會申請記憶體空間存放變量值,然後将變量值的記憶體位址綁定給變量名,以變量的定義age=18為例,如下圖

Python變量及垃圾回收機制

通過變量名即可引用到對應的值

Python變量及垃圾回收機制

變量名的命名規範

變量名隻能由數字、字母、下劃線任意組合

變量名不能以數字開頭、建議不要以下劃線開頭(有特殊含義)

變量名不能與關鍵字沖突,常見關鍵字如下

變量名的命名一定要做到見名知意(見名知意是核心,無論變量多長)

Python變量及垃圾回收機制

變量名的命名風格

駝峰體

大駝峰:所有單詞首字母大寫

小駝峰:第一個字母小寫其餘首字母大寫

下劃線:單詞與單詞之間下劃線隔開,Python推薦使用該風格

Python變量及垃圾回收機制

變量值的三大特性

變量的值:value

變量的記憶體位址:id

變量的資料類型:type

檢視變量值三大特性的方式如下,我們将會在運算中用到變量值的三大特性:

Python變量及垃圾回收機制

常量指在程式運作過程中不會改變的量。

在程式運作過程中,有些值是固定的、不應該被改變,比如圓周率 3.1415926…

在Python中沒有真正意義上的常量,約定俗成是用全部大寫的變量名表示常量。如PI = 3.1415926。是以單從文法層面去講,常量的使用與變量完全一緻。

PS:在其他程式設計語言中是存在真正意義上的常量,定義了就無法修改。

Python變量及垃圾回收機制

解釋器在執行到定義變量的文法時,會申請記憶體空間來存放變量的值,而記憶體的容量是有限的,這就涉及到變量值所占用記憶體空間的回收問題,當一個變量值沒有用了(簡稱垃圾)就應該将其占用的記憶體給回收掉,那什麼樣的變量值是沒有用的呢?

由于變量名是通路到變量值的唯一方式,是以當一個變量值不再關聯任何變量名時,我們就無法再通路到該變量值了,該變量值就是沒有用的,就應該被當成一個垃圾回收。毫無疑問,記憶體空間的申請與回收是非常耗費精力的事情,而且存在很大的危險性,稍有不慎就有可能引發記憶體溢出問題,好在Python解釋器提供了自動的垃圾回收機制來幫我們解決了這件事。

Python變量及垃圾回收機制

垃圾資料是在記憶體中沒有任何變量名指向的資料;

垃圾回收機制(簡稱GC)是Python解釋器自帶的一種機制,專門用來回收不可用的變量值所占用的記憶體空間。

程式運作過程中會申請大量的記憶體空間,而對于一些無用的記憶體空間如果不及時清理的話會導緻記憶體使用殆盡(記憶體溢出),導緻程式崩潰,是以管理記憶體是一件重要且繁雜的事情,而Python解釋器自帶的垃圾回收機制把程式員從繁雜的記憶體管理中解放出來。

Python變量及垃圾回收機制

在定義變量時,變量名與變量值都是需要存儲的,分别對應記憶體中的兩塊區域:棧區與堆區

變量名與值記憶體位址的關聯關系存放于棧區

變量值存放于堆區,記憶體管理回收的則是堆區的内容

定義了兩個變量x = 10、y = 20,詳解如下圖

Python變量及垃圾回收機制

當我們執行x = y時,記憶體中的棧區與堆區變化如下

Python變量及垃圾回收機制

直接引用指的是從棧區出發直接引用到的記憶體位址

間接引用指的是從棧區出發引用到堆區後,再通過進一步引用才能到達的記憶體位址

示例如下:

圖解如下:

Python變量及垃圾回收機制

Python的GC子產品主要運用了“引用計數”(reference counting)來跟蹤和回收垃圾。在引用計數的基礎上,還可以通過“标記-清除”(mark and sweep)解決容器對象可能産生的循環引用的問題,并且通過“分代回收”(generation collection)以空間換取時間的方式來進一步提高垃圾回收的效率。

引用計數就是:變量值被變量名關聯的次數

如:age = 18

變量值18被關聯了一個變量名age,稱之為引用計數為1

Python變量及垃圾回收機制

引用計數增加:

age=18 (此時,變量值18的引用計數為1)

m=age (把age的記憶體位址給了m,此時,m,age都關聯了18,是以變量值18的引用計數為2)

Python變量及垃圾回收機制

引用計數減少:

age=10(名字age先與值18解除關聯,再與10建立了關聯,變量值18的引用計數為1)

del m(del的意思是解除變量名m與變量值18的關聯關系,此時,變量18的引用計數為0)

Python變量及垃圾回收機制

值18的引用計數一旦變為0,其占用的記憶體位址就應該被解釋器的垃圾機制回收。

變量值被關聯次數的增加或減少,都會引發引用計數機制的執行(增加或減少值的引用計數),這存在明顯的效率問題。

Python變量及垃圾回收機制

問題一:循環引用

引用計數機制存在着一個緻命的弱點,即循環引用(也稱交叉引用)

Python變量及垃圾回收機制

循環引用會導緻:值不再被任何名字關聯,但是值的引用計數并不會為0,應該被回收但不能被回收,什麼意思呢?試想一下,請看如下操作

此時,隻剩下清單1與清單2之間的互相引用,

Python變量及垃圾回收機制

但此時兩個清單的引用計數均不為0,但兩個清單不再被任何其他對象關聯,沒有任何人可以再引用到它們,是以它倆占用記憶體空間應該被回收,但由于互相引用的存在,每一個對象的引用計數都不為0,是以這些對象所占用的記憶體永遠不會被釋放,是以循環引用是緻命的,這與手動進行記憶體管理所産生的記憶體洩露毫無差別。

是以Python引入了“标記-清除” 與“分代回收”來分别解決引用計數的循環引用與效率低的問題

Python變量及垃圾回收機制

解決方案:标記-清除

容器對象(比如:list,set,dict,class,instance)都可以包含對其他對象的引用,是以都可能産生循環引用。而“标記-清除”計數就是為了解決循環引用的問題。

标記/清除算法的做法是當應用程式可用的記憶體空間被耗盡的時,就會停止整個程式,然後進行兩項工作,第一項則是标記,第二項則是清除

基于上例的循環引用,當我們同時删除L1與L2時,會清理掉棧區中L1與L2的内容以及直接引用關系

Python變量及垃圾回收機制

這樣在啟用标記清除算法時,發現棧區内不再有L1與L2(隻剩下堆區内二者的互相引用),于是清單1與清單2都沒有被标記為存活,二者會被清理掉,這樣就解決了循環引用帶來的記憶體洩漏問題。

問題二:效率問題

基于引用計數的回收機制,每次回收記憶體,都需要把所有對象的引用計數都周遊一遍,這是非常耗時間的,于是引入了分代回收來提高回收效率,分代回收采用的是用“空間換時間”的政策。

解決方案:分代回收

分代:

分代回收的核心思想是:在曆經多次掃描的情況下,都沒有被回收的變量,Gc機制就會認為,該變量是常用變量,Gc對其掃描的頻率會降低,具體實作原理如下:

分代指的是根據存活時間來為變量劃分不同等級(也就是不同的代)。

新定義的變量,放到新生代這個等級中,假設每隔1分鐘掃描新生代一次,如果發現變量依然被引用,那麼該對象的權重(權重本質就是個整數)加一,當變量的權重大于某個設定得值(假設為3),會将它移動到更高一級的青春代,青春代的Gc掃描的頻率低于新生代(掃描時間間隔更長),假設5分鐘掃描青春代一次,這樣每次Gc需要掃描的變量的總個數就變少了,節省了掃描的總時間,接下來,青春代中的對象,也會以同樣的方式被移動到老年代中。也就是等級(代)越高,被垃圾回收機制掃描的頻率越低。

回收:

回收依然是使用引用計數作為回收的依據

Python變量及垃圾回收機制

雖然分代回收可以起到提升效率的效果,但也存在一定的缺點:

例如一個變量剛剛從新生代移入青春代,該變量的綁定關系就解除了,該變量應該被回收,但青春代的掃描頻率低于新生代,這就到導緻了應該被回收的垃圾沒有得到及時的清理。

沒有十全十美的方案:

毫無疑問,如果沒有分代回收,即引用計數機制一直不停地對所有變量進行全體掃描,可以更及時地清理掉垃圾占用的記憶體,但這種一直不停地對所有變量進行全體掃描的方式效率極低,是以我們隻能将二者中和。

綜上

垃圾回收機制是在清理垃圾&釋放記憶體的大背景下,允許分代回收以極小部分垃圾不會被及時釋放為代價,以此換取引用計數整體掃描頻率的降低,進而提升其性能,這是一種以空間換時間的解決方案。