<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>

          面試官:Python 對象的垃圾回收策略是什么?

          共 4611字,需瀏覽 10分鐘

           ·

          2020-12-30 17:28

          作者 | wedo實驗君
          來源 | Python中文社區(qū)

          1. 引言

          引用在各種編程語言中都有涉及,如java中值傳遞引用傳遞。python的對象引用也是學(xué)習(xí)python過程中需要特別關(guān)注的一個知識點(diǎn),特別是對函數(shù)參數(shù)傳遞,可能會引起不必要的BUG。本文將對引用做一個梳理,內(nèi)容涉及如下:
          • 變量和賦值
          • 可變對象和不可變對象
          • 函數(shù)參數(shù)的引用
          • 淺拷貝和深拷貝
          • 垃圾回收
          • 弱引用

          2. python引用

          2.1 變量和賦值

          任何一個python對象都有標(biāo)簽,類型和值三個屬性。標(biāo)簽在對象創(chuàng)建后直到內(nèi)存回收就保持不變,可以理解為內(nèi)存地址。
          python在給變量賦值,會把賦值的對象的內(nèi)存地址賦值給變量。也就是說python的變量是地址引用式的變量。引用語義的方式,存儲的只是一個變量的值所在的內(nèi)存地址,而不是這個變量的值本身。
          可以通過is或者比較id()的判斷是否引用的是同一個內(nèi)存地址的變量。
          • == 是比較兩個對象的內(nèi)容是否相等,即兩個對象的值是否相等
          • is同時檢查對象的值和內(nèi)存地址??梢酝ㄟ^is判斷是否是同一個對象
          • id() 列出變量的內(nèi)存地址的編號
          #?這個例子a?和?b?兩個變量共同指向了同一個內(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
          • 初始化賦值:變量的每一次初始化,都開辟了一個新的空間,將新內(nèi)容的地址賦值給變量

          • 變量賦值:引用地址的傳遞

          2.2 可變對象和不可變對象

          python中的一切都是對象。python對象又分為可變對象和不可變對象。二者的區(qū)別在于對象的值在不改變內(nèi)存地址的情況下是否可修改。
          • 可變對象包括字典dict、列表list、集合set、手動聲明的類對象等
          • 不可變對象包括數(shù)字int float、字符str、None、元組tuple等
          下面舉幾個典型的例子:
          • list 可變對象,內(nèi)容變更地址不變
            a?=?[1,?2,?3]
            print(id(a))
            a.append(5)
            print(id(a))
          • 不可變對象(常用的共享地址或緩存)
            #?較小整數(shù)頻繁被使用,python采用共享地址方式來管理
            a?=?1
            b?=?1
            print(a?is?b)?#?True

            #?對于單詞類str,python采用共享緩存的方式來共享地址
            a?=?'hello'
            b?=?'hello'
            print(a?is?b)?#?True
          • 不可變對象(不共享地址)
            a?=?(1999,?1)
            b?=?(1999,?1)
            print(a?is?b)?#?False

            a?=?'hello?everyone'
            b?=?'hello?everyone'
            print(a?is?b)?#?False
          • 元組的相對不可變型

            #?元組的里元素是可變,改變可變的元素,不改變元組的引用
            a?=?(1999,?[1,?2])
            ida?=?id(a)
            a[-1].append(3)
            idb?=?id(a)

            print(ida?==?idb)?#?True
          這里之所以要提到變量可變和不可變的特性,其實主要是想說明變量的引用其實和變量的可變和不可變沒有直接的關(guān)系。變量可變和不可變主要著眼點(diǎn)是變量可不可以修改,注意這個修改不是通過賦值的操作來完成。
          a?=?[1,?2,?3]
          print(id(a))
          a?=?a?+?[5]
          print(id(a))
          #?前后兩個變量a,?已經(jīng)不是同一個地址了

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

          python中函數(shù)的傳參方式是共享傳參,即函數(shù)的形參是實參中各個引用的副本(別名)。函數(shù)會修改是可變對象的實參(表示的同一個對象);而不會改變實參的引用。
          def?func(d):
          ????d['a']?=?10
          ????d['b']?=?20????#?改變了外部實參的值????????
          ????d?=?{'a':?0,?'b':?1}??#?賦值操作,?局部d貼向了新的標(biāo)識
          ????print(d)?#?{'a':?0,?'b':?1}

          d?=?{}
          func(d)
          print(d)?#?{'a':?10,?'b':?20}
          建議不要寫上面例子的代碼,局部變量和全局變量的名稱一樣,盡量編碼,否則很容易出bug而不自知。
          函數(shù)的參數(shù)的默認(rèn)值避免使用可變參數(shù),盡量用None來代替。原因是函數(shù)的默認(rèn)值是作為函數(shù)對象的屬性,如果默認(rèn)值是可變對象,而且修改了它,那邊后續(xù)的函數(shù)對象都會受到影響。
          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 淺拷貝和深拷貝

          對于可變對象,我們要時刻注意它的可變性,特別是對賦值或者拷貝后的變量做內(nèi)容修改操作的時候,需要考慮下是否會影響到原始變量的值,如果程序有bug,可以往這方面想一想。這里講一下拷貝即建立副本??截愑袃煞N:
          • 淺拷貝:只復(fù)制頂層的對象,對于有嵌套數(shù)據(jù)結(jié)構(gòu),內(nèi)部的元素還是原有對象的引用,這時候需要特別注意
          • 深拷貝:復(fù)制了所有對象,遞歸式的復(fù)制所有對象。復(fù)制后的對象和原來的對象是完全不同的對象。對于不可變對象來說,淺拷貝和深拷貝都是一樣的地址。但是對于嵌套了可變對象元素的情況,就有所不同
          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)?#?改變不可變對象中可變元素的內(nèi)容
          print(test_a?is?test_b)?#?True
          print(test_a?is?test_c)?#?False
          print(test_c)?#?(1,?2,?[3,?4])
          對于可變對象,只要拷貝,就創(chuàng)建了一個新的頂層對象。如果是淺拷貝,內(nèi)部嵌套的可變對象只是拷貝引用,這樣就會相互影響。深拷貝就不會有這種問題。
          l1?=?[3,?[66,?55,?44],?(2,?3,?4)]
          l2?=?list(l1)?#?l2是l1的淺拷貝

          #?頂層改變不會相互影響,因為是兩個不同對象
          l1.append(50)?
          print(l1)?#?3,?[66,?55,?44],?(2,?3,?4),?50]
          print(l2)?#?[3,?[66,?55,?44],?(2,?3,?4)]

          #?嵌套可變元素,淺拷貝共享一個地址
          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)]

          #?嵌套不可變元素,不可變元素的操作是創(chuàng)建一個新的對象,所以不影響
          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對于垃圾回收,采取的是引用計數(shù)為主,標(biāo)記-清除+分代回收為輔的回收策略
          • 引用計數(shù):python可以給所有的對象(內(nèi)存中的區(qū)域)維護(hù)一個引用計數(shù)的屬性,在一個引用被創(chuàng)建或復(fù)制的時候,讓python,把相關(guān)對象的引用計數(shù)+1;相反當(dāng)引用被銷毀的時候就把相關(guān)對象的引用計數(shù)-1。當(dāng)對象的引用計數(shù)減到0時,認(rèn)為整個python中不會再有變量引用這個對象,所以就可以把這個對象所占據(jù)的內(nèi)存空間釋放出來了??梢酝ㄟ^sys.getrefcount()來查看對象的引用
          • 分代回收: 分代回收主要是為了提高垃圾回收的效率。對象的創(chuàng)建和消費(fèi)的頻率不一樣。由于python在垃圾回收前需要檢測是否是垃圾,是否回收,然后再回收。當(dāng)對象很多的時候,垃圾檢測的耗時變得很大,效率很低。python采用的對對象進(jìn)行分代,按不同的代進(jìn)行不同的頻率的檢測。代等級的規(guī)則根據(jù)對象的生命時間來判斷,比如一個對象連續(xù)幾次檢測都是可達(dá)的,這個對象代的等級高,降低檢測頻率。python中默認(rèn)把所有對象分成三代。第0代包含了最新的對象,第2代則是最早的一些對象
          • 循環(huán)引用:一個對象直接或者間接引用自己本身,引用鏈形成一個環(huán)。這樣改對象的引用計數(shù)永遠(yuǎn)不可能為0。所有能夠引用其他對象的對象都被稱為容器(container). 循環(huán)引用只能發(fā)生容器之間發(fā)生. Python的垃圾回收機(jī)制利用了這個特點(diǎn)來尋找需要被釋放的對象。
          import?sys
          a?=?[1,?2]
          b?=?a

          print(sys.getrefcount(a))?#?3?命令本身也是一次引用
          del?b
          print(sys.getrefcount(a))?#?2?

          3. 弱引用

          弱引用在許多高級語言中都存在。如前所述,當(dāng)對象的引用計數(shù)為0時,垃圾回收機(jī)制就會銷毀對象。但有時候需要引用對象,但不希望增加引用計數(shù)。這樣有什么好處?
          • 應(yīng)用在緩存中,只存在一定的時間存在。當(dāng)它引用的對象存在時,則對象可用,當(dāng)對象不存在時,就返回None
          • 不增加引用計數(shù),在循環(huán)引用使用,就降低內(nèi)存泄露的可能性
          這就是弱引用weak reference,不會增加對象的引用數(shù)量。引用的目標(biāo)對象稱為 所指對象 (referent)。
          import?weakref
          a_set?=?{0,1}
          wref?=?weakref.ref(a_set)?#?建立弱引用
          print(wref())?#?{0,1}
          a_set?=?{2,?3,?4}?#?原來的a_set?引用計數(shù)為0,垃圾回收
          print(wref())?#?None?#?所值對象被垃圾回收,?弱引用也消失為None
          弱引用一般使用時weakref集合, weakref.WeakValueDictionary, weakref.WeakKeyDictionary兩者的區(qū)別是一個是值進(jìn)行弱引用,一個是可以進(jìn)行弱引用;另外還有weakref.WeakSet

          4. 總結(jié)

          本文描述python中引用相關(guān)的幾個方面,希望對大家有幫助??偨Y(jié)如下:
          • 對象賦值就完成引用,變量是地址引用式的變量
          • 要時刻注意,所以引用可變對象對象的改變,是否導(dǎo)致共同引用的變量值得變化
          • 函數(shù)會修改是可變對象的實參
          • 淺拷貝只是copy頂層,如果存在內(nèi)部嵌套可變對象,要注意,copy的還是引用
          • 對象的引用計數(shù)為0時,就開始垃圾回收
          • 弱引用為增加引用計數(shù),與被所指對象共存亡,而不影響循環(huán)引用



          瀏覽 67
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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 | 在线毛片网站 | 美女考逼|