<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 代碼中的垃圾回收機(jī)制

          共 2789字,需瀏覽 6分鐘

           ·

          2020-08-16 11:13


          點(diǎn)擊上方“Python爬蟲與數(shù)據(jù)挖掘”,進(jìn)行關(guān)注

          回復(fù)“書籍”即可獲贈(zèng)Python從入門到進(jìn)階共10本電子書

          仰天大笑出門去,我輩豈是蓬蒿人。


          攝影:產(chǎn)品經(jīng)理
          跟產(chǎn)品經(jīng)理從花鳥市場(chǎng)淘回來的小花花

          GNE: 新聞網(wǎng)頁正文通用抽取器[1]更新了0.2.1版本,大幅度提高了正文的提取速度。在開發(fā)這個(gè)版本的時(shí)候,我遇到了一個(gè)非常奇怪的 Bug,最終發(fā)現(xiàn)是由于垃圾回收機(jī)制和內(nèi)存重用機(jī)制導(dǎo)致的。今天我們來看看這個(gè)問題。

          問題背景

          先來看一段代碼:

          圖1

          這段代碼讀取tests/163/9.html這個(gè)文件里面的 HTML 代碼,分別獲取 下面的所有標(biāo)簽內(nèi)部的所有標(biāo)簽中的文本。說起來可能有點(diǎn)繞口,我舉個(gè)例子。

                  

          世界

          分別獲取

          標(biāo)簽和

          標(biāo)簽下面的標(biāo)簽中的文本,也就是你好世界。

          但這段代碼有個(gè)問題,就是對(duì)于嵌套結(jié)構(gòu)的標(biāo)簽,會(huì)重復(fù)提取。例如:

              

          首先,獲取

          標(biāo)簽下面的標(biāo)簽,獲取到的是你好所在的標(biāo)簽。但是,獲取

          標(biāo)簽下面的標(biāo)簽時(shí),獲取的仍然是同一個(gè)標(biāo)簽。

          這樣一來,在上圖代碼里面第15-20行就會(huì)重復(fù)執(zhí)行兩次。

          為了提高代碼的運(yùn)行效率,我們引入緩存,記錄每一個(gè)標(biāo)簽的分析結(jié)果,如果發(fā)現(xiàn)一個(gè)標(biāo)簽已經(jīng)被分析了,就直接使用緩存的結(jié)果,避免重復(fù)分析。

          于是,代碼修改成下面這樣:

          圖2

          代碼第18行的str(element)對(duì)應(yīng)了這個(gè)節(jié)點(diǎn)的內(nèi)存地址,如下圖所示:

          圖3

          這段代碼看起來似乎沒有什么問題,但在實(shí)際提取數(shù)據(jù)的時(shí)候,發(fā)現(xiàn)提取的結(jié)果不太正常。

          薛定諤的 Element

          為了調(diào)試這個(gè)問題,我對(duì)代碼做了一下修改:

          圖4

          可以看到,同一個(gè) HTML 標(biāo)簽,之前緩存的結(jié)果竟然跟新提取的不一樣。

          于是,我想看看每次提取的時(shí)候,對(duì)應(yīng)的 element 是哪個(gè),但卻發(fā)生了更詭異的事情,我們做一個(gè)看起來對(duì)代碼不會(huì)有任何影響的改動(dòng):

          圖5

          圖4里面,我們直接把element_text_list緩存起來。圖5里面,我們把[element_text_list, element]緩存起來,讀取的時(shí)候,讀取這個(gè)列表的下標(biāo)為0的元素。也就是說,這個(gè)緩存的element我們根本不使用。

          但奇怪的事情就這樣發(fā)生了,問題消失了!在圖4大量打印的同一個(gè)標(biāo)簽,緩存的數(shù)據(jù)跟提取的數(shù)據(jù)不一致!,在圖5里面卻一條都沒有打印。這樣修改以后,GNE 的提取的結(jié)果就正確了。

          但為什么會(huì)發(fā)生這種事情呢?難道說跟緩存的結(jié)果有關(guān)系?那么我們把列表里面的 element改成其他數(shù)據(jù)看看:

          圖6

          僅僅是把element改成了數(shù)字1,Bug 又出現(xiàn)了。

          它似乎知道我在試圖去觀察它,當(dāng)我嘗試用代碼去觀察 element時(shí),它就一切正常。當(dāng)我不觀察它時(shí),它就會(huì)出問題。薛定諤的 element。

          看不見的手

          遇事不決,量子力學(xué)。這個(gè)問題跟量子力學(xué)實(shí)際上沒有關(guān)系。導(dǎo)致這個(gè)詭異情況發(fā)生的原因,是一個(gè)一直運(yùn)行在 Python 里面,但是你常常忽略的機(jī)制——垃圾回收。

          Python 會(huì)把不再使用的對(duì)象清理掉,從而釋放內(nèi)存。當(dāng)我們執(zhí)行一個(gè) for 循環(huán)時(shí):

          for element in element_list:    a = element.xpath('//xxx')    b = element.xpath('.//text()')    c = 1 + 1

          循環(huán)第一次執(zhí)行的時(shí)候,生成第一個(gè)element對(duì)象,但是這個(gè)對(duì)象在循環(huán)第二次執(zhí)行的時(shí)候就被新的element對(duì)象覆蓋了。因?yàn)闆]有其他地方繼續(xù)使用第一個(gè) element 對(duì)象,它的引用計(jì)數(shù)歸零,Python 的垃圾回收機(jī)制就會(huì)把它清理掉。它占用的內(nèi)存空間也會(huì)被釋放出來。

          但如果換一種寫法:

          cache = []for element in element_list:    a = element.xpath('//xxx')    b = element.xpath('.//text()')    c = 1 + 1    cache.append(element)

          由于列表cache中包含了對(duì)每個(gè) element 對(duì)象的引用,導(dǎo)致第一次循環(huán)生成的element對(duì)象的引用計(jì)數(shù)不為0,垃圾回收機(jī)制不會(huì)回收它,它始終占用了一塊內(nèi)存區(qū)域。這塊區(qū)域不會(huì)被其他數(shù)據(jù)使用。那么每次循環(huán),新的element對(duì)象都會(huì)新申請(qǐng)一塊內(nèi)存區(qū)域來存放數(shù)據(jù),于是就等價(jià)于每一個(gè)不同的 element 節(jié)點(diǎn)對(duì)應(yīng)了不同的內(nèi)存地址。

          在示例代碼里面,大家注意element_flag = str(element)這一行,它的值類似于,這里的十六進(jìn)制數(shù)字0x1087ba638對(duì)應(yīng)了這個(gè)對(duì)象在內(nèi)存里面的地址。

          一開始,我有一個(gè)不正確的假設(shè),我以為str(element)的值,對(duì)應(yīng)的 HTML 里面的每個(gè)節(jié)點(diǎn)。同一個(gè)節(jié)點(diǎn),多次執(zhí)行,結(jié)果都一樣,不同的節(jié)點(diǎn),多次執(zhí)行,結(jié)果都不一樣。

          但實(shí)際上這是不正確的。因?yàn)槿绻耙粋€(gè)節(jié)點(diǎn)的內(nèi)存區(qū)域被垃圾回收了,那么這個(gè)區(qū)域會(huì)被重新分配,新來的節(jié)點(diǎn)可能碰巧會(huì)放到這個(gè)地方,這就導(dǎo)致兩個(gè)不同的 標(biāo)簽,當(dāng)你執(zhí)行str(element)時(shí),他們打印出來的結(jié)果都是相同的。但是實(shí)際上他們的正文不一樣。

          而當(dāng)我使用element_text_cache[element_flag] = [element_text_list, element]時(shí),由于每個(gè)element對(duì)象不會(huì)被回收,于是就不會(huì)出現(xiàn)不同的節(jié)點(diǎn)互相覆蓋的問題,所以它的工作就符合了預(yù)期。

          解決問題

          所以,bug 的根本原因在于,我不應(yīng)該使用str(element)作為緩存的 Key,應(yīng)該找一個(gè)跟 HTML 節(jié)點(diǎn)一一對(duì)應(yīng)的東西來作為 Key。顯然,使用 XPath 更好。

          于是,修改代碼,把element_flag改成 XPath:

          圖7

          問題得以解決。

          參考資料

          [1]

          GNE: 新聞網(wǎng)頁正文通用抽取器: https://github.com/kingname/GeneralNewsExtractor

          -------------------?End?-------------------

          往期精彩文章推薦:

          歡迎大家點(diǎn)贊,留言,轉(zhuǎn)發(fā),轉(zhuǎn)載,感謝大家的相伴與支持

          想加入Python學(xué)習(xí)群請(qǐng)?jiān)诤笈_(tái)回復(fù)【入群

          萬水千山總是情,點(diǎn)個(gè)【在看】行不行

          /今日留言主題/

          隨便說一兩句吧~~

          瀏覽 66
          點(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视频 | 最近中文字幕免费MV第一季歌词怀孕 | 亚洲男人天堂视频 | 欧美成人手机版 | 人人干人人摸人人操 |