<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 4751字,需瀏覽 10分鐘

           ·

          2020-12-28 10:19

          1. 引言

          引用在各種編程語(yǔ)言中都有涉及,如java中值傳遞引用傳遞。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 變量和賦值

          任何一個(gè)python對(duì)象都有標(biāo)簽,類(lèi)型和值三個(gè)屬性。標(biāo)簽在對(duì)象創(chuàng)建后直到內(nèi)存回收就保持不變,可以理解為內(nèi)存地址。
          python在給變量賦值,會(huì)把賦值的對(duì)象的內(nèi)存地址賦值給變量。也就是說(shuō)python的變量是地址引用式的變量。引用語(yǔ)義的方式,存儲(chǔ)的只是一個(gè)變量的值所在的內(nèi)存地址,而不是這個(gè)變量的值本身。
          可以通過(guò)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ì)象

          python中的一切都是對(duì)象。python對(duì)象又分為可變對(duì)象和不可變對(duì)象。二者的區(qū)別在于對(duì)象的值在不改變內(nèi)存地址的情況下是否可修改。
          • 可變對(duì)象包括字典dict、列表list、集合set、手動(dòng)聲明的類(lèi)對(duì)象等
          • 不可變對(duì)象包括數(shù)字int float、字符str、None、元組tuple等
          下面舉幾個(gè)典型的例子:
          • 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
          這里之所以要提到變量可變和不可變的特性,其實(shí)主要是想說(shuō)明變量的引用其實(shí)和變量的可變和不可變沒(méi)有直接的關(guān)系。變量可變和不可變主要著眼點(diǎn)是變量可不可以修改,注意這個(gè)修改不是通過(guò)賦值的操作來(lái)完成。
          a?=?[1,?2,?3]
          print(id(a))
          a?=?a?+?[5]
          print(id(a))
          #?前后兩個(gè)變量a,?已經(jīng)不是同一個(gè)地址了

          2.3 函數(shù)參數(shù)的引用

          python中函數(shù)的傳參方式是共享傳參,即函數(shù)的形參是實(shí)參中各個(gè)引用的副本(別名)。函數(shù)會(huì)修改是可變對(duì)象的實(shí)參(表示的同一個(gè)對(duì)象);而不會(huì)改變實(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}
          建議不要寫(xiě)上面例子的代碼,局部變量和全局變量的名稱(chēng)一樣,盡量編碼,否則很容易出bug而不自知。
          函數(shù)的參數(shù)的默認(rèn)值避免使用可變參數(shù),盡量用None來(lái)代替。原因是函數(shù)的默認(rèn)值是作為函數(shù)對(duì)象的屬性,如果默認(rèn)值是可變對(duì)象,而且修改了它,那邊后續(xù)的函數(shù)對(duì)象都會(huì)受到影響。
          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 淺拷貝和深拷貝

          對(duì)于可變對(duì)象,我們要時(shí)刻注意它的可變性,特別是對(duì)賦值或者拷貝后的變量做內(nèi)容修改操作的時(shí)候,需要考慮下是否會(huì)影響到原始變量的值,如果程序有bug,可以往這方面想一想。這里講一下拷貝即建立副本。拷貝有兩種:
          • 淺拷貝:只復(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])
          對(duì)于可變對(duì)象,只要拷貝,就創(chuàng)建了一個(gè)新的頂層對(duì)象。如果是淺拷貝,內(nèi)部嵌套的可變對(duì)象只是拷貝引用,這樣就會(huì)相互影響。深拷貝就不會(huì)有這種問(wèn)題。
          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 垃圾回收

          python對(duì)于垃圾回收,采取的是引用計(jì)數(shù)為主,標(biāo)記-清除+分代回收為輔的回收策略。
          • 引用計(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. 弱引用

          弱引用在許多高級(jí)語(yǔ)言中都存在。如前所述,當(dāng)對(duì)象的引用計(jì)數(shù)為0時(shí),垃圾回收機(jī)制就會(huì)銷(xiāo)毀對(duì)象。但有時(shí)候需要引用對(duì)象,但不希望增加引用計(jì)數(shù)。這樣有什么好處?
          • 應(yīng)用在緩存中,只存在一定的時(shí)間存在。當(dāng)它引用的對(duì)象存在時(shí),則對(duì)象可用,當(dāng)對(duì)象不存在時(shí),就返回None
          • 不增加引用計(jì)數(shù),在循環(huán)引用使用,就降低內(nèi)存泄露的可能性
          這就是弱引用weak reference,不會(huì)增加對(duì)象的引用數(shù)量。引用的目標(biāo)對(duì)象稱(chēng)為 所指對(duì)象 (referent)。
          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
          弱引用一般使用時(shí)weakref集合, weakref.WeakValueDictionary, weakref.WeakKeyDictionary兩者的區(qū)別是一個(gè)是值進(jìn)行弱引用,一個(gè)是可以進(jìn)行弱引用;另外還有weakref.WeakSet

          4. 總結(jié)

          本文描述python中引用相關(guān)的幾個(gè)方面,希望對(duì)大家有幫助。總結(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ě)作


          贊 賞 作 者


          更多閱讀



          5分鐘掌握在 Cython 中使用 C++


          5 分鐘掌握 Python 中常見(jiàn)的配置文件


          5 分鐘掌握 Python 中的 Hook 鉤子函數(shù)

          特別推薦


          程序員摸魚(yú)指南


          為你精選的硅谷極客資訊,
          來(lái)自FLAG巨頭開(kāi)發(fā)者、技術(shù)、創(chuàng)投一手消息




          點(diǎn)擊下方閱讀原文加入社區(qū)會(huì)員

          瀏覽 51
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  www.男人的天堂 | 先锋AV网址 | 国产精品久久久久久久午夜 | а中文在线天堂 | 青青青操逼电影 |