5分鐘掌握 Python 對(duì)象的引用

1. 引言
值傳遞和引用傳遞。python的對(duì)象引用也是學(xué)習(xí)python過(guò)程中需要特別關(guān)注的一個(gè)知識(shí)點(diǎn),特別是對(duì)函數(shù)參數(shù)傳遞,可能會(huì)引起不必要的BUG。本文將對(duì)引用做一個(gè)梳理,內(nèi)容涉及如下:變量和賦值 可變對(duì)象和不可變對(duì)象 函數(shù)參數(shù)的引用 淺拷貝和深拷貝 垃圾回收 弱引用
2. python引用
2.1 變量和賦值
is或者比較id()的判斷是否引用的是同一個(gè)內(nèi)存地址的變量。==是比較兩個(gè)對(duì)象的內(nèi)容是否相等,即兩個(gè)對(duì)象的值是否相等is同時(shí)檢查對(duì)象的值和內(nèi)存地址。可以通過(guò)is判斷是否是同一個(gè)對(duì)象id()列出變量的內(nèi)存地址的編號(hào)
#?這個(gè)例子a?和?b?兩個(gè)變量共同指向了同一個(gè)內(nèi)存空間
a?=?[1,?2,?3]
c?=?[1,?2,?3]
print(a?is?c)?#?False
print(a?==?c)?#?True
b?=?a
a.append(5)
print(a?is?b)?#?True
初始化賦值:變量的每一次初始化,都開(kāi)辟了一個(gè)新的空間,將新內(nèi)容的地址賦值給變量
變量賦值:引用地址的傳遞
2.2 可變對(duì)象和不可變對(duì)象
可變對(duì)象包括字典dict、列表list、集合set、手動(dòng)聲明的類(lèi)對(duì)象等 不可變對(duì)象包括數(shù)字int float、字符str、None、元組tuple等
list 可變對(duì)象,內(nèi)容變更地址不變 a?=?[1,?2,?3]
print(id(a))
a.append(5)
print(id(a))不可變對(duì)象(常用的共享地址或緩存) #?較小整數(shù)頻繁被使用,python采用共享地址方式來(lái)管理
a?=?1
b?=?1
print(a?is?b)?#?True
#?對(duì)于單詞類(lèi)str,python采用共享緩存的方式來(lái)共享地址
a?=?'hello'
b?=?'hello'
print(a?is?b)?#?True不可變對(duì)象(不共享地址) a?=?(1999,?1)
b?=?(1999,?1)
print(a?is?b)?#?False
a?=?'hello?everyone'
b?=?'hello?everyone'
print(a?is?b)?#?False元組的相對(duì)不可變型
#?元組的里元素是可變,改變可變的元素,不改變?cè)M的引用
a?=?(1999,?[1,?2])
ida?=?id(a)
a[-1].append(3)
idb?=?id(a)
print(ida?==?idb)?#?True
a?=?[1,?2,?3]
print(id(a))
a?=?a?+?[5]
print(id(a))
#?前后兩個(gè)變量a,?已經(jīng)不是同一個(gè)地址了
2.3 函數(shù)參數(shù)的引用
def?func(d):
????d['a']?=?10
????d['b']?=?20????#?改變了外部實(shí)參的值????????
????d?=?{'a':?0,?'b':?1}??#?賦值操作,?局部d貼向了新的標(biāo)識(shí)
????print(d)?#?{'a':?0,?'b':?1}
d?=?{}
func(d)
print(d)?#?{'a':?10,?'b':?20}
class?bus():
????def?__init__(self,?param=[]):
????????self.param?=?param
????????
????def?test(self,?elem):
????????self.param.append(elem)
b?=?bus([2,?3])
b.param?#?[2,?3]
c?=?bus()
c.test(3)
c.param?#?[3]
d?=?bus()
d.param?#?[3]??#?c?中修改了默認(rèn)值的引用的內(nèi)容
2.4 淺拷貝和深拷貝
淺拷貝:只復(fù)制頂層的對(duì)象,對(duì)于有嵌套數(shù)據(jù)結(jié)構(gòu),內(nèi)部的元素還是原有對(duì)象的引用,這時(shí)候需要特別注意 深拷貝:復(fù)制了所有對(duì)象,遞歸式的復(fù)制所有對(duì)象。復(fù)制后的對(duì)象和原來(lái)的對(duì)象是完全不同的對(duì)象。對(duì)于不可變對(duì)象來(lái)說(shuō),淺拷貝和深拷貝都是一樣的地址。但是對(duì)于嵌套了可變對(duì)象元素的情況,就有所不同
test_a?=?(1,?2,?3)
test_b?=?copy.copy(test_a)
test_c?=?copy.deepcopy(test_a)
print(test_a?is?test_b)?#?True
print(test_a?is?test_c)?#?True
test_a[2].append(5)?#?改變不可變對(duì)象中可變?cè)氐膬?nèi)容
print(test_a?is?test_b)?#?True
print(test_a?is?test_c)?#?False
print(test_c)?#?(1,?2,?[3,?4])
l1?=?[3,?[66,?55,?44],?(2,?3,?4)]
l2?=?list(l1)?#?l2是l1的淺拷貝
#?頂層改變不會(huì)相互影響,因?yàn)槭莾蓚€(gè)不同對(duì)象
l1.append(50)?
print(l1)?#?3,?[66,?55,?44],?(2,?3,?4),?50]
print(l2)?#?[3,?[66,?55,?44],?(2,?3,?4)]
#?嵌套可變?cè)兀瑴\拷貝共享一個(gè)地址
l1[1].append(100)
print(l1)?#?[3,?[66,?55,?44,?100],?(2,?3,?4),?50]
print(l2)?#?[3,?[66,?55,?44,?100],?(2,?3,?4)]
#?嵌套不可變?cè)兀豢勺冊(cè)氐牟僮魇莿?chuàng)建一個(gè)新的對(duì)象,所以不影響
l1[2]?+=?(2,3)
print(l1)?#?[3,?[66,?55,?44,?100],?(2,?3,?4,?2,?3),?50]
print(l2)?#[3,?[66,?55,?44,?100],?(2,?3,?4)]
2.5 垃圾回收
引用計(jì)數(shù):python可以給所有的對(duì)象(內(nèi)存中的區(qū)域)維護(hù)一個(gè)引用計(jì)數(shù)的屬性,在一個(gè)引用被創(chuàng)建或復(fù)制的時(shí)候,讓python,把相關(guān)對(duì)象的引用計(jì)數(shù)+1;相反當(dāng)引用被銷(xiāo)毀的時(shí)候就把相關(guān)對(duì)象的引用計(jì)數(shù)-1。當(dāng)對(duì)象的引用計(jì)數(shù)減到0時(shí),認(rèn)為整個(gè)python中不會(huì)再有變量引用這個(gè)對(duì)象,所以就可以把這個(gè)對(duì)象所占據(jù)的內(nèi)存空間釋放出來(lái)了。可以通過(guò) sys.getrefcount()來(lái)查看對(duì)象的引用分代回收: 分代回收主要是為了提高垃圾回收的效率。對(duì)象的創(chuàng)建和消費(fèi)的頻率不一樣。由于python在垃圾回收前需要檢測(cè)是否是垃圾,是否回收,然后再回收。當(dāng)對(duì)象很多的時(shí)候,垃圾檢測(cè)的耗時(shí)變得很大,效率很低。python采用的對(duì)對(duì)象進(jìn)行分代,按不同的代進(jìn)行不同的頻率的檢測(cè)。代等級(jí)的規(guī)則根據(jù)對(duì)象的生命時(shí)間來(lái)判斷,比如一個(gè)對(duì)象連續(xù)幾次檢測(cè)都是可達(dá)的,這個(gè)對(duì)象代的等級(jí)高,降低檢測(cè)頻率。python中默認(rèn)把所有對(duì)象分成三代。第0代包含了最新的對(duì)象,第2代則是最早的一些對(duì)象 循環(huán)引用:一個(gè)對(duì)象直接或者間接引用自己本身,引用鏈形成一個(gè)環(huán)。這樣改對(duì)象的引用計(jì)數(shù)永遠(yuǎn)不可能為0。所有能夠引用其他對(duì)象的對(duì)象都被稱(chēng)為容器(container). 循環(huán)引用只能發(fā)生容器之間發(fā)生. Python的垃圾回收機(jī)制利用了這個(gè)特點(diǎn)來(lái)尋找需要被釋放的對(duì)象。
import?sys
a?=?[1,?2]
b?=?a
print(sys.getrefcount(a))?#?3?命令本身也是一次引用
del?b
print(sys.getrefcount(a))?#?2?
3. 弱引用
應(yīng)用在緩存中,只存在一定的時(shí)間存在。當(dāng)它引用的對(duì)象存在時(shí),則對(duì)象可用,當(dāng)對(duì)象不存在時(shí),就返回None 不增加引用計(jì)數(shù),在循環(huán)引用使用,就降低內(nèi)存泄露的可能性
import?weakref
a_set?=?{0,1}
wref?=?weakref.ref(a_set)?#?建立弱引用
print(wref())?#?{0,1}
a_set?=?{2,?3,?4}?#?原來(lái)的a_set?引用計(jì)數(shù)為0,垃圾回收
print(wref())?#?None?#?所值對(duì)象被垃圾回收,?弱引用也消失為None
4. 總結(jié)
對(duì)象賦值就完成引用,變量是地址引用式的變量 要時(shí)刻注意,所以引用可變對(duì)象對(duì)象的改變,是否導(dǎo)致共同引用的變量值得變化 函數(shù)會(huì)修改是可變對(duì)象的實(shí)參 淺拷貝只是copy頂層,如果存在內(nèi)部嵌套可變對(duì)象,要注意,copy的還是引用 對(duì)象的引用計(jì)數(shù)為0時(shí),就開(kāi)始垃圾回收 弱引用為增加引用計(jì)數(shù),與被所指對(duì)象共存亡,而不影響循環(huán)引用
作者簡(jiǎn)介:wedo實(shí)驗(yàn)君, 數(shù)據(jù)分析師;熱愛(ài)生活,熱愛(ài)寫(xiě)作
贊 賞 作 者

更多閱讀
特別推薦

點(diǎn)擊下方閱讀原文加入社區(qū)會(huì)員
評(píng)論
圖片
表情
