1. 程式人生 > >09-Python面向物件-物件的生命週期,以及週期方法

09-Python面向物件-物件的生命週期,以及週期方法

學習地址:

撩課-Python大資料+人工智慧1
撩課-Python大資料+人工智慧2
撩課-Python大資料+人工智慧3
撩課-Python大資料+人工智慧4
撩課-Python大資料+人工智慧5
撩課-Python大資料+人工智慧6
撩課-Python-GUI程式設計-PyQt5

1.概念

生命週期

			指的是一個物件, 從誕生到消亡的過程
			當一個物件被建立時, 會在記憶體中分配相應的記憶體空間進行儲存
			當這個物件不再使用, 為了節約記憶體, 就會把這個物件釋放

2.涉及問題

		如何監聽一個物件的生命過程?
		Python是如何掌控一個物件的生命?

3.監聽物件生命週期

		__new__方法
			當我們建立一個物件是, 用於給這個物件分配記憶體的方法
			通過攔截這個方法, 可以修改物件的建立過程
				比如:單例設計模式
		__init__方法
			每個物件例項化的時候,都會自動執行這個方法
			可以在這個方法裡面,初始化一些例項屬性
		__del__方法
			當物件被釋放的時候呼叫這個方法
			可用於在這個方法中清理資源

4.記憶體管理機制

儲存方面

			1. 在Python中萬物皆物件
				不存在基本資料型別
				0,  1.2,  True, False, "abc"
					這些全都是物件
			2. 所有物件, 都會在記憶體中開闢一塊空間進行儲存
				會根據不同的型別以及內容, 開闢不同的空間大小進行儲存
				返回該空間的地址給外界接收(稱為"引用"), 用於後續對這個物件的操作
					可通過id()函式獲取記憶體地址(10進位制)
					通過hex()函式可以檢視對應的16進位制地址
			3. 對於整數和短小的字元, Python會進行快取; 不會建立多個相同物件
				此時, 被多次賦值, 只會有多份引用
			4. 容器物件, 儲存的其他物件, 僅僅是其他物件的引用, 並不是其他物件本身
				比如字典, 列表, 元組這些"容器物件"
				全域性變數是由一個大字典進行引用
					global()檢視

垃圾回收方面

1).引用計數器

概念

					一個物件, 會記錄著自身被引用的個數
					每增加一個引用, 這個物件的引用計數會自動+1
					每減少一個引用, 這個物件的引用計數會自動-1

舉例

					引用計數+1場景
						物件被建立
							p1 = Person()
						物件被引用
							p2 = p1
						物件被作為引數,傳入到一個函式中
							log(p1)
							這裡注意會+2, 因為內部有兩個屬性引用著這個引數
						物件作為一個元素,儲存在容器中
							l = [p1]
					引用計數-1場景
						物件的別名被顯式銷燬
							del p1
						物件的別名被賦予新的物件
							p1 = 123
						一個物件離開它的作用域
							一個函式執行完畢時
							內部的區域性變數關聯的物件, 它的引用計數就會-1
						物件所在的容器被銷燬,或從容器中刪除物件

檢視引用計數

					import sys
					sys.getrefcount(物件)
						注意會大一

垃圾回收

主要作用

					從經歷過"引用計數器機制"仍未被釋放的物件中, 找到"迴圈引用", 幹掉相關物件底層機制(瞭解&難)

怎樣找到"迴圈引用"?

                         1. 收集所有的"容器物件", 通過一個雙向連結串列進行引用
							容器物件
								可以引用其他物件的物件
									列表
									元組
									字典
									自定義類物件
									...
							非容器物件
								不能引用其他物件的物件
									數值
									字串
									布林
									...
								注意: 針對於這些物件的記憶體, 有其他的管理機制
						2. 針對於每一個"容器物件", 通過一個變數gc_refs來記錄當前對應的引用計數
						3. 對於每個"容器物件",找到它引用的"容器物件", 並將這個"容器物件"的引用計數 -1
						4. 經過步驟3之後, 如果一個"容器物件"的引用計數為0, 就代表這玩意可以被回收了, 肯定是"迴圈引用"導致它活到現在的

如何提升查詢"迴圈引用"的效能?

						如果程式當中建立了很多個物件, 而針對於每一個物件都要參與"檢測"過程; 則會非常的耗費效能
						所以, 基於這個問題, 產生了一種假設:
							越命大的物件, 越長壽
							假設一個物件10次檢測都沒給它幹掉, 那認定這個物件一定很長壽, 就減少這貨的"檢測頻率"
						基於這種假設, 設計了一套機制
							分代回收
								機制
									1. 預設一個物件被創建出來後, 屬於 0 代
									2. 如果經歷過這一代"垃圾回收"後, 依然存活, 則劃分到下一代
									3. "垃圾回收"的週期順序為
										0代"垃圾回收"一定次數, 會觸發 0代和1代回收
										1代"垃圾回收"一定次數, 會觸發0代, 1代和2代回收
								檢視和設定相關引數
									import gc
									print(gc.get_threshold())
									gc.set_threshold(700, 10, 5)
							垃圾回收器當中, 新增的物件個數-消亡的物件個數 , 達到一定的閾值時, 才會觸發, 垃圾檢測

垃圾回收時機(掌握&簡單)

					1. 自動回收
						觸發條件
							開啟垃圾回收機制
								gc.enable()
									開啟垃圾回收機制(預設開啟)
								gc.disable()
									關閉垃圾回收機制
								gc.isenabled()
									判定是否開啟
							並且
							達到了垃圾回收的閾值
								垃圾回收器中, 新增的物件個數和釋放的物件個數之差到達某個閾值
								涉及方法
									gc.get_threshold()
										獲取自動回收閾值
									gc.set_threshold()
										設定自動回收閾值
					2. 手動回收
						觸發條件
							gc.collect()
							執行一次垃圾回收(開關狀態無效)

特殊場景

場景條件

					Python2.x版本下, 迴圈引用, 並且有一個物件都實現了__del__方法
						概念
							兩個物件互相引用物件, 誰的引用計數都是1
						兩個物件都

導致結果

					無法釋放, 進行記憶體回收

底層原因

					無法判別先釋放哪個物件, 呼叫哪一個del方法

解決思路

					手動解除迴圈引用
					方式1
						預防
							儘可能避免迴圈引用產生
						實現
							一方使用弱引用代替
					方式2
						治療
							在迴圈引用的產生的前提下
						實現
							當刪除一個引用, 確定以後不再使用時, 手動清空對其他容器物件的引用
		            測量物件的引用個數
			              輔助工具
			              objgraph
					      http://mg.pov.lt/objgraph/
				         	xdot
					        graphviz