<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是如何管理內(nèi)存的?

          共 3633字,需瀏覽 8分鐘

           ·

          2020-12-14 15:27

          1. 內(nèi)存管理關(guān)我屁事?

          內(nèi)存管理是指在程序的運行過程中,分配內(nèi)容和回收內(nèi)存的過程。如果只分配,不回收,電腦上那點內(nèi)存很快就被用光。

          好的程序能夠高效的使用內(nèi)存,不好的程序會造成過多的內(nèi)存消耗,內(nèi)存泄露,棧溢出,程序死翹翹。

          幸運的是,Python和Java等高級語言會自動管理內(nèi)存的分配和回收。

          但程序員仍然必須具有一定內(nèi)存管理知識!

          小白需要內(nèi)存管理知識避免犯低級錯誤,高手需要內(nèi)容管理知識來優(yōu)化程序性能,就像賽車手調(diào)教汽車的性能。

          舉個例子:生成包含1億個隨機字符串的序列,小白可能用list,而有點經(jīng)驗的會用generator。內(nèi)存的使用效率可能差了一億倍。

          generator出現(xiàn)的一個重要原因就是省內(nèi)存。

          2. 可變數(shù)據(jù)類型和不可變數(shù)據(jù)類型

          當(dāng)我們調(diào)用函數(shù)的時候,我們需要傳遞參數(shù),這時候變量從一個函數(shù)傳遞到了另一個函數(shù)。這個傳遞過程發(fā)生了什么?

          被傳遞的對象是被復(fù)制了一份呢?還是就是同一份呢?

          我們得先理解變量的存儲結(jié)構(gòu),尤其是list等容器類的存儲。

          看下面的代碼:

          name?=?'麥?zhǔn)?
          print(id(name))??#打印內(nèi)存地址:140628480727248
          name?=?'張三'
          print(id(name))??#打印內(nèi)存地址:140628480727056

          當(dāng)我們定義了一個變量name = '麥?zhǔn)?,在內(nèi)存中有兩個部分:

          • 一個是name這個變量名
          • 一個是真正存儲'麥?zhǔn)?這個字符串的對象

          上面的代碼在內(nèi)存中的過程是這樣的:

          1. 開始變量名name指向了“麥?zhǔn)濉薄?/section>
          2. 后來變量名name指向了“張三”。

          注意:這時候“麥?zhǔn)濉辈辉儆凶兞渴褂昧?,可能會被垃圾回收器銷毀掉。

          對于一個復(fù)雜的數(shù)據(jù)類型,比如list,原理是相同的但是更復(fù)雜:

          cities?=?['北京',?'上海',?'廣州',?'深圳']
          print(id(cities))
          cities[3]='雄安'
          print(id(cities))

          cities一直指向內(nèi)存中的列表對象,列表中的值改變不會改變cities在內(nèi)存中的地址。改變的是列表指向的另外一個對象的地址。

          這里的要點:

          • name等字符串是不可變的,當(dāng)變量的值變化了,實際上生成了一個新的變量,內(nèi)存地址變化了。

            基本數(shù)據(jù)類型都是不可變的,還有整數(shù),小數(shù)等都是不可變。當(dāng)一個變量的值發(fā)生了變化,實際上是創(chuàng)建了一個新的對象。

          • 而cities是個list,是可變的,里面的值會發(fā)生變化,內(nèi)存地址沒有發(fā)生變化??勺冞€包括dict等。

          3.參數(shù)傳遞

          理解了變量的內(nèi)存存儲結(jié)構(gòu),來看參數(shù)傳遞的問題,在函數(shù)調(diào)用過程中,參數(shù)傳遞到底是傳遞了什么?是復(fù)制了一份嗎?

          簡單說:函數(shù)參數(shù)和返回值的傳遞都是傳遞的變量指向的內(nèi)存地址!

          import?random
          name?=?'麥?zhǔn)?
          print(f'name的地址:{id(name)}')
          ?
          def?shuai_score(person):
          ????print(f'name的地址:{id(person)}')
          ????score?=?random.randint(1,?10)
          ????print(f'score的地址:{id(score)}')
          ????return?score
          ?
          mscore?=?shuai_score(name)
          print(f'mscore的地址:{id(mscore)}')

          對于普通對象(不可變類型)和容器類變量可能看起來效果不一樣,但實際上原理是一樣的。如下面的代碼:

          • 全局變量name指向張三,這一直都沒有變過。
          • 局部變量name本來是指向張三的,但是后來指向了李四,這并不影響全局變量還是指向張三。
          name?=?'zhangsan'
          ?
          def?hello(name):
          ????print('傳進來的是:'+?name)
          ????name?=?'lisi'
          ????print('name被改成了'?+?name)
          ?
          print(name)

          對于列表,改變列表的值會影響全局變量。

          cities?=?['北京',?'上海',?'廣州',?'深圳']
          print(f'全局cities的地址:{id(cities)}')
          ?
          def?change_city(cities):
          ????print(f'局部cities的地址:{id(cities)}')
          ????cities[0]?=?'雄安'
          ????print(f'局部cities的地址:{id(cities)}')
          ?
          change_city(cities)
          print(cities)
          print(f'全局cities的地址:{id(cities)}')

          4. 引用次數(shù)和垃圾回收器

          上面的知識和內(nèi)存管理有什么關(guān)系?當(dāng)然有!

          內(nèi)存管理的基本原理:回收掉沒用的內(nèi)存!

          怎么判定有用沒用呢:如果還有變量在使用就不要回收,沒變量使用了就干掉它。

          好可怕,做個打工人也一樣吧?如果你還有用就繼續(xù)打工,沒用了就干掉。

          上面的例子中:'麥?zhǔn)?這個對象沒用了,因為變量name指向了新的對象'張三'。

          Python用引用次數(shù),英文叫做reference count來表示有幾個變量在使用這個對象。

          通過一個叫做垃圾回收器(英文是Garbage Collector)的后臺線程定期查看是否有些變量的引用次數(shù)為0,并清理掉引用次數(shù)為0的對象。

          引用次數(shù)是如何產(chǎn)生的?

          • 當(dāng)對象被賦值給新的變量,引用次數(shù)就會增加
          • 當(dāng)變量作為參數(shù)傳遞給其他函數(shù),引用次數(shù)就會增加

          引用次數(shù)如何減少?

          • 當(dāng)函數(shù)執(zhí)行結(jié)束,引用對象的變量不再有效,引用次數(shù)減少

          我們可以通過sys.getrefcount查看一個對象的引用次數(shù):

          import?sys
          name?=?'麥?zhǔn)?
          print(sys.getrefcount(name))?#打印4

          上面的代碼會打印出4次!

          這是怎么回事?明明只有一次??!這4個引用是:

          1. name變量
          2. getrefcount:當(dāng)name被傳遞給getrefcount函數(shù)的時候,函數(shù)的參數(shù)也指向了它。
          3. Python解釋器:為了執(zhí)行這個腳本,Python解釋器也保留了一個引用,直到腳本結(jié)束。只針對腳本全局變量。
          4. 編譯優(yōu)化器:當(dāng)執(zhí)行腳本的時候,優(yōu)化器會嘗試優(yōu)化字節(jié)碼,所以也產(chǎn)生了一次引用。這個引用是臨時的,很快就會消失。

          如果不在腳本中運行,直接在交互式Python下運行,refcount是2,因為沒有后面兩個引用:

          >>>?import?sys
          >>>?name?=?'麥?zhǔn)?
          >>>?print(sys.getrefcount(name))??#打印2

          再來看一段代碼:

          import?sys
          name?=?'麥?zhǔn)?
          print(sys.getrefcount(name))??#打印4
          name2?=?name
          print(sys.getrefcount(name))??#打印5
          name3?=?name?
          print(sys.getrefcount(name))??#打印6

          因為name2和name3都指向了'麥?zhǔn)?,所以引用次數(shù)不斷增加。

          注意:getrefcount函數(shù)執(zhí)行完,它產(chǎn)生的引用就消失了,所以不會因為調(diào)用了3次而增加3個。

          5. 手工回收

          正常情況下,回收垃圾這事兒都是Python的垃圾回收器的活。

          就像賽車手要自己調(diào)教汽車,必要的時候我們也可以自己動手。

          import?sys,?gc

          #?這個函數(shù)創(chuàng)建一個自己指向自己的列表
          def?create_cycle():
          ????list?=?[8,?9,?10]
          ????list.append(list)

          print("創(chuàng)建垃圾...")
          for?i?in?range(8):
          ????create_cycle()

          print("我們來強制回收...")
          n?=?gc.collect()
          print("清理掉的無頭尸體:",?n)
          print("沒清理的垃圾:",?gc.garbage)

          執(zhí)行結(jié)果如下:

          創(chuàng)建垃圾...
          因為與引用,所以不會被自動回收...
          我們來強制回收...
          清理掉的無頭尸體:?8
          沒清理的垃圾:?[]

          運送垃圾的車有自己的時間點,比如每天早上6點。但如果垃圾太多了,也可以打電話讓他們馬上來過清理垃圾。

          垃圾回收器有它自己的運行節(jié)奏。我們可以調(diào)用gc.collect()讓它馬上執(zhí)行回收操作。

          6. 要點

          1. 知道可變數(shù)據(jù)類型,不可變數(shù)據(jù)類型,以及參數(shù)傳遞原理
          2. 理解list等可變數(shù)據(jù)類型作為參數(shù)傳遞后修改的是同一個對象
          3. 合理使用對象,避免占用太多內(nèi)存,比如大數(shù)據(jù)情況下使用generator而不是list
          4. 避免變量循環(huán)引用,造成引用數(shù)永遠不為零,可能造成不可回收而引起內(nèi)存泄露。

          今天這些對大部分人可能夠用了。其實內(nèi)存管理和垃圾回收是比較高級的話題,屬于編程的深水區(qū)。有興趣的建議多研究一下,深水區(qū)里去游一下。

          瀏覽 52
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  开心情色站 | 成人伊人观看视频 | 日韩高清一区二区 | 天天爽天天澡天天爽视频 - 百度 无码毛片一区二区三区四区五区六区 | 亚洲在线视频免费 |