1 對(duì)象存儲(chǔ)
- 在Python中萬(wàn)物皆對(duì)象
不存在基本數(shù)據(jù)類型蝙茶,`0, 1.2, True, False, "abc"`等隆夯,這些全都是對(duì)象
- 所有對(duì)象, 都會(huì)在內(nèi)存中開辟一塊空間進(jìn)行存儲(chǔ)
2.1 會(huì)根據(jù)不同的類型以及內(nèi)容, 開辟不同的空間大小進(jìn)行存儲(chǔ)
2.2 返回該空間的地址給外界接收(稱為"引用"), 用于后續(xù)對(duì)這個(gè)對(duì)象的操作
2.3 可通過(guò) id() 函數(shù)獲取內(nèi)存地址(10進(jìn)制)
2.4 通過(guò) hex() 函數(shù)可以查看對(duì)應(yīng)的16進(jìn)制地址
class Person:
pass
p = Person()
print(p)
print(id(p))
print(hex(id(p)))
>>>> 打印結(jié)果
<__main__.Person object at 0x107030470>
4412605552
0x107030470
- 對(duì)于整數(shù)和短小的字符, Python會(huì)進(jìn)行緩存; 不會(huì)創(chuàng)建多個(gè)相同對(duì)象
此時(shí), 被多次賦值, 只會(huì)有多份引用
num1 = 2
num2 = 2
print(id(num1), id(num2))
>>>> 打印結(jié)果
4366584464 4366584464
- 容器對(duì)象, 存儲(chǔ)的其他對(duì)象, 僅僅是其他對(duì)象的引用, 并不是其他對(duì)象本身
4.1 比如字典, 列表, 元組這些"容器對(duì)象"
4.2 全局變量是由一個(gè)大字典進(jìn)行引用
4.3 可通過(guò) global() 查看
2 對(duì)象回收
2.1 引用計(jì)數(shù)器
2.1.1概念
- 一個(gè)對(duì)象, 會(huì)記錄著自身被引用的個(gè)數(shù)
- 每增加一個(gè)引用, 這個(gè)對(duì)象的引用計(jì)數(shù)會(huì)自動(dòng)+1
- 每減少一個(gè)引用, 這個(gè)對(duì)象的引用計(jì)數(shù)會(huì)自動(dòng)-1
2.1.2 計(jì)數(shù)器變化常見(jiàn)場(chǎng)景
- 引用計(jì)數(shù)+1場(chǎng)景
1厘肮、對(duì)象被創(chuàng)建
p1 = Person()
2轴脐、對(duì)象被引用
p2 = p1
3大咱、對(duì)象被作為參數(shù)碴巾,傳入到一個(gè)函數(shù)中
log(p1)
這里注意會(huì)+2, 因?yàn)閮?nèi)部有兩個(gè)屬性引用著這個(gè)參數(shù)
4、對(duì)象作為一個(gè)元素啤月,存儲(chǔ)在容器中
l = [p1]
- +1 情況3說(shuō)明:內(nèi)部有兩個(gè)屬性引用著這個(gè)參數(shù)
Python2.x 下打印 :_globals__
和func_globals
引用該參數(shù)對(duì)象谎仲,計(jì)數(shù)+2
Python3.x 下打又E怠:則只有一個(gè)_globals__
引用該對(duì)象辙诞,同樣計(jì)數(shù)+2
import sys
class Person:
pass
p_xxx = Person() # 1
print(sys.getrefcount(p_xxx))
def log(obj):
print(sys.getrefcount(obj))
log(p_xxx)
# for attr in dir(log):
# print(attr, getattr(log, attr))
>>>> 打印結(jié)果
2
4
- 引用計(jì)數(shù)-1場(chǎng)景
1、對(duì)象的別名被顯式銷毀
del p1
2祈搜、對(duì)象的別名被賦予新的對(duì)象
p1 = 123
3夭问、一個(gè)對(duì)象離開它的作用域
一個(gè)函數(shù)執(zhí)行完畢時(shí)
內(nèi)部的局部變量關(guān)聯(lián)的對(duì)象, 它的引用計(jì)數(shù)就會(huì)-1
4捧杉、對(duì)象所在的容器被銷毀味抖,或從容器中刪除對(duì)象
2.1.3 查看引用計(jì)數(shù)
- 注意計(jì)數(shù)器會(huì)>1灰粮,因?yàn)閷?duì)象在 getrefcount方法中被引用
import sys
sys.getrefcount(對(duì)象)
import sys
class Person:
pass
p1 = Person() # 1
print(sys.getrefcount(p1)) # 2
p2 = p1 # 2
print(sys.getrefcount(p1)) # 3
del p2 # 1
print(sys.getrefcount(p1)) # 2
del p1
# print(sys.getrefcount(p1)) #error熔脂,因?yàn)樯弦恍写a執(zhí)行類p1對(duì)象已經(jīng)銷毀
>>>> 打印結(jié)果
2
3
2
2.2 循環(huán)引用
- 對(duì)象間互相引用柑肴,導(dǎo)致對(duì)象不能通過(guò)引用計(jì)數(shù)器進(jìn)行銷毀
# 循環(huán)引用
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
- 使用 objgraph 模塊
- objgraph.count() 可以查看, 垃圾回收器, 跟蹤的對(duì)象個(gè)數(shù)
# 正常情況
import objgraph
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
# 刪除 p, d之后, 對(duì)應(yīng)的對(duì)象是否被釋放掉
del p
del d
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印結(jié)果
1
1
0
0
# 循環(huán)引用
import objgraph
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
p.pet = d
d.master = p
# 刪除 p, d之后, 對(duì)應(yīng)的對(duì)象是否被釋放掉
del p
del d
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印結(jié)果
1
1
1
1
2.2 垃圾回收
2.2.1 主要作用
- 從經(jīng)歷過(guò)
引用計(jì)數(shù)器機(jī)制
仍未被釋放的對(duì)象中, 找到循環(huán)引用
對(duì)象, 并回收相關(guān)對(duì)象
2.2.2 垃圾回收底層機(jī)制
2.2.2.1 如何找到 循環(huán)引用
對(duì)象
- 收集所有的"容器對(duì)象", 通過(guò)一個(gè)雙向鏈表進(jìn)行引用
* 容器對(duì)象
可以引用其他對(duì)象的對(duì)象
列表
元組
字典
自定義類對(duì)象
...
* 非容器對(duì)象
不能引用其他對(duì)象的對(duì)象
數(shù)值
字符串
布爾
...
注意: 針對(duì)于這些非容器對(duì)象的內(nèi)存, 有其他的管理機(jī)制
- 針對(duì)于每一個(gè)"容器對(duì)象", 通過(guò)一個(gè)變量gc_refs來(lái)記錄當(dāng)前對(duì)應(yīng)的引用計(jì)數(shù)
- 對(duì)于每個(gè)"容器對(duì)象",找到它引用的"容器對(duì)象", 并將這個(gè)"容器對(duì)象"的引用計(jì)數(shù) -1
- 經(jīng)過(guò)步驟3之后, 如果一個(gè)"容器對(duì)象"的引用計(jì)數(shù)為0, 就代表這個(gè)對(duì)象可以被回收, 而它肯定是因?yàn)?循環(huán)引用"導(dǎo)致不能被回收(存活到現(xiàn)在)
2.2.2.2 如何提升查找"循環(huán)引用"的性能?
1 問(wèn)題:
- 如果程序當(dāng)中創(chuàng)建了很多個(gè)對(duì)象, 而針對(duì)于每一個(gè)對(duì)象都要參與"檢測(cè)"過(guò)程; 則會(huì)非常的耗費(fèi)性能
2 假設(shè):
- 基于這個(gè)問(wèn)題, 產(chǎn)生了一種假設(shè):
1. 越命大的對(duì)象, 越長(zhǎng)壽
2. 假設(shè)一個(gè)對(duì)象10次檢測(cè)都沒(méi)給它干掉, 就認(rèn)定這個(gè)對(duì)象一定很長(zhǎng)壽, 就減少這貨的"檢測(cè)頻率"
3 設(shè)計(jì)機(jī)制:
-
分待回收
機(jī)制
1. 默認(rèn)一個(gè)對(duì)象被創(chuàng)建出來(lái)后, 屬于 0 代
2. 如果經(jīng)歷過(guò)這一代"垃圾回收"后, 依然存活, 則劃分到下一代
3. "垃圾回收"的周期順序?yàn)? 0代"垃圾回收"一定次數(shù), 會(huì)觸發(fā) 0代和1代回收
1代"垃圾回收"一定次數(shù), 會(huì)觸發(fā)0代, 1代和2代回收
2.2.2.3 垃圾檢測(cè)時(shí)機(jī)
- 垃圾回收器當(dāng)中, 新增的對(duì)象個(gè)數(shù)-消亡的對(duì)象個(gè)數(shù) , 達(dá)到一定的閾值時(shí), 才會(huì)觸發(fā), 垃圾檢測(cè)
- 通過(guò) gc 模塊查看當(dāng)前垃圾回收機(jī)制促發(fā)條件及設(shè)置條件
import gc
# 獲取
print(gc.get_threshold())
>>>> 打印結(jié)果
(700, 10, 10)
# 參數(shù)1秽荞,700扬跋;代表:新增的對(duì)象個(gè)數(shù)-消亡的對(duì)象個(gè)數(shù) == 700 時(shí)會(huì)促發(fā)垃圾檢測(cè)時(shí)機(jī)
# 參數(shù)2趁猴,10彪见;代表:當(dāng)?shù)?代對(duì)象檢測(cè)次數(shù)達(dá)到10次時(shí)候余指,會(huì)促發(fā)0代和1代對(duì)象的檢測(cè)
# 參數(shù)3酵镜,10淮韭;代表:當(dāng)?shù)?代對(duì)象檢測(cè)次數(shù)達(dá)到10次時(shí)候靠粪,會(huì)促發(fā)0代占键、1代和2代對(duì)象的檢測(cè)
# 設(shè)置
gc.set_threshold(200, 5, 5)
print(gc.get_threshold())
>>>> 打印結(jié)果
(200, 5, 5)
2.2.3 垃圾回收時(shí)機(jī)
2.2.3.1 自動(dòng)回收
- 觸發(fā)條件
* 開啟垃圾回收機(jī)制
* 且達(dá)到啟動(dòng)垃圾回收對(duì)應(yīng)的閾值
- 開啟垃圾回收機(jī)制
gc.enable()
開啟垃圾回收機(jī)制(默認(rèn)開啟)
gc.disable()
關(guān)閉垃圾回收機(jī)制
gc.isenabled()
判定是否開啟
- 啟動(dòng)垃圾回收對(duì)應(yīng)的閾值
- 垃圾回收器中, 新增的對(duì)象個(gè)數(shù)和釋放的對(duì)象個(gè)數(shù)之差到達(dá)某個(gè)閾值
查看方法
gc.get_threshold()
獲取自動(dòng)回收閾值
gc.set_threshold()
設(shè)置自動(dòng)回收閾值
# 自動(dòng)回收
import gc
gc.disable()
print(gc.isenabled())
gc.enable()
print(gc.isenabled())
print(gc.get_threshold())
gc.set_threshold(1000, 15, 5)
一般會(huì)將對(duì)應(yīng)的閾值設(shè)置成更大的值畔乙,這樣可以提高程序性能
2.2.3.2 手動(dòng)回收
- 觸發(fā)條件
- 調(diào)用 gc 模塊的collection() 方法
gc.collect(generation=None)
"""
collect([generation]) -> n
With no arguments, run a full collection. The optional argument
may be an integer specifying which generation to collect. A ValueError
is raised if the generation number is invalid.
The number of unreachable objects is returned.
"""
- generation 參數(shù):如果為空時(shí),則表明全代回收钥庇;指定代數(shù)的話,則會(huì)檢測(cè)該代之前的所有對(duì)象皮服,如:generation = 2硫眯,則檢測(cè)0两入、1和2代的對(duì)象
- 不管之前垃圾回收機(jī)制是否開啟敲才,調(diào)用 collection() 方法后都會(huì)執(zhí)行一次垃圾回收
- 通過(guò) objgraph 查看該類實(shí)例引用計(jì)數(shù)
import objgraph
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印結(jié)果
0
0
- 因循環(huán)引用情況剃氧,引用計(jì)數(shù)器處理不了
import objgraph
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印結(jié)果
1
1
- 手動(dòng)促發(fā)垃圾回收朋鞍,處理循環(huán)引用對(duì)象
import objgraph
import gc
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
gc.collect() #手動(dòng)觸發(fā)垃圾回收
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印結(jié)果
0
0
- 即使將垃圾回收機(jī)制關(guān)閉滥酥,調(diào)用gc.collect() 時(shí)候都會(huì)啟動(dòng)觸發(fā)垃圾回收畦幢,執(zhí)行完后并未自動(dòng)開啟
import objgraph
import gc
gc.disable() #手動(dòng)關(guān)閉垃圾回收
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
gc.collect() #手動(dòng)啟動(dòng)垃圾回收
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
print(gc.isenabled()) # 檢查關(guān)閉后是否自動(dòng)開啟
>>>> 打印結(jié)果
0
0
False
3 循環(huán)引用 - 版本兼容方案宇葱、弱引用打破循環(huán)引用
- 循環(huán)引用通過(guò)手動(dòng)啟動(dòng)垃圾回收機(jī)制進(jìn)行回收
import objgraph
import gc
# 1. 定義了兩個(gè)類
class Person:
pass
class Dog:
pass
# 2. 根據(jù)這兩個(gè)類, 創(chuàng)建出兩個(gè)實(shí)例對(duì)象
p = Person()
d = Dog()
# 3. 讓兩個(gè)實(shí)例對(duì)象之間互相引用, 造成循環(huán)引用
p.pet = d
d.master = p
# 4. 嘗試刪除可到達(dá)引用之后, 測(cè)試真實(shí)對(duì)象是否有被回收
del p
del d
# 5. 通過(guò)"引用計(jì)數(shù)機(jī)制"無(wú)法回收; 需要借助"垃圾回收機(jī)制"進(jìn)行回收
gc.collect()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印結(jié)果
0
0
- 可以通過(guò)重寫類方法
__del__
查看類實(shí)例被釋放時(shí)標(biāo)識(shí)
- Python3.x 環(huán)境
import objgraph
import gc
class Person:
def __del__(self):
print("Person對(duì)象, 被釋放了")
pass
class Dog:
def __del__(self):
print("Dog對(duì)象, 被釋放了")
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
gc.collect()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印結(jié)果
Person對(duì)象, 被釋放了
Dog對(duì)象, 被釋放了
0
0
- Python2.x 環(huán)境時(shí):
如果循環(huán)引用對(duì)象任意一個(gè)類里面實(shí)現(xiàn)了__del__
方法吗氏,都會(huì)導(dǎo)致垃圾回收機(jī)制無(wú)法正常運(yùn)行
并且不會(huì)執(zhí)行__del__
方法
import objgraph
import gc
class Person:
def __del__(self):
print("Person對(duì)象, 被釋放了")
pass
class Dog:
def __del__(self):
print("Dog對(duì)象, 被釋放了")
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
gc.collect()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印結(jié)果
1
1
- Python2.x 中循環(huán)引用類都沒(méi)有重寫
__del__
時(shí),則垃圾回收機(jī)制正常
import objgraph
import gc
class Person:
# def __del__(self):
# print("Person對(duì)象, 被釋放了")
pass
class Dog:
# def __del__(self):
# print("Dog對(duì)象, 被釋放了")
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
gc.collect()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印結(jié)果
0
0
4 打破循環(huán)引用的方法
- 手動(dòng)強(qiáng)制將循環(huán)引用的屬性置 None膀哲。
p.pet = None
Python2.x 環(huán)境
import objgraph
import gc
# 1. 定義了兩個(gè)類
class Person:
def __del__(self):
print("Person對(duì)象, 被釋放了")
pass
class Dog:
def __del__(self):
print("Dog對(duì)象, 被釋放了")
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
p.pet = None #強(qiáng)制置 None
del p
del d
gc.collect()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印結(jié)果
Dog對(duì)象, 被釋放了
Person對(duì)象, 被釋放了
0
0
- 通過(guò)弱引用方式打破循環(huán)引用
Python2.x 環(huán)境
- 導(dǎo)入 weakref 模塊
- 一對(duì)一弱引用
d.master = weakref.ref(p)
import objgraph
import gc
import weakref
# 1. 定義了兩個(gè)類
class Person:
def __del__(self):
print("Person對(duì)象, 被釋放了")
pass
class Dog:
def __del__(self):
print("Dog對(duì)象, 被釋放了")
pass
p = Person()
d = Dog()
p.pet = d
d.master = weakref.ref(p)
del p
del d
gc.collect()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印結(jié)果
Person對(duì)象, 被釋放了
Dog對(duì)象, 被釋放了
0
0
- 弱引用一對(duì)多
# 單個(gè)寫法
p.pets = {"dog": weakref.ref(d1), "cat": weakref.ref(c1)}
或
字典形式
p.pets = weakref.WeakValueDictionary({"dog": d1, "cat": c1})
一般還有 如下方法
WeakKeyDictionary
WeakSet
···