雖然咱們如今得益於時代和技術的發展,不用再擔憂內存的問題;可是遙想當年,都是巴不得一個鋼鏰掰成倆份用,因此我就想深刻了解一下,在python中內存分配的一些小祕密。html
首先我會頻繁地用到sys
模塊裏的getsizeof()
方法,簡單介紹下:python
舉個例子:app
import sys a = [1, 2] b = [a, a] # 即 [[1, 2], [1, 2]] # a、b 都只有兩個元素,因此直接佔用的大小相等(只和元素個數有關,和元素是什麼無關) sys.getsizeof(a) # 結果:80 sys.getsizeof(b) # 結果:80
上例說明了一件事:一個靜態建立的列表,若是隻包含兩個元素,那它自身佔用的內存就是 80 字節,無論其元素所指向的對象是什麼。rest
咱們已經知道如何測量了,那麼咱們就一塊兒來探索吧!code
不知道你們有沒有好奇過,空的對象如空字符串、空列表、空字典,他們到底佔不佔用內存呢?若是佔用內存,又佔用多少呢?htm
直接揭曉答案吧:對象
import sys print(sys.getsizeof('')) # 49 print(sys.getsizeof([])) # 64 print(sys.getsizeof(tuple())) # 48 print(sys.getsizeof(set())) # 224 print(sys.getsizeof(dict())) # 240 # 參照: print(sys.getsizeof(None)) # 16 print(sys.getsizeof(False)) # 24 print(sys.getsizeof(0)) # 24 print(sys.getsizeof(1)) # 28 print(sys.getsizeof(True)) # 28
可見,雖然都是空對象,可是這些對象在內存分配上並不爲「空」,並且分配得還挺大(記住這幾個數字哦,後面會考)。blog
排序下就是:基礎數字<空元組 < 空字符串 < 空列表 < 空集合 < 空字典。排序
若是必定要解釋的話,個人理解就是:不一樣類在建立的時候自帶的屬性不同,如引用計數、使用量信息等等,還有一部份內存是預分配的。內存
空對象並不爲空,一部分緣由是 Python 解釋器爲它們預分配了一些初始空間。在不超出初始內存的狀況下,每次新增元素,就使用已有內存,於是避免了再去申請新的內存。
那麼,若是初始內存被分配完以後,新的內存是怎麼分配的呢?
import sys s = 'abcdefghijklmnopqrstuvwxyz' a = [] for i in s: a.append(i) print(len(a) , f"a 的內存佔用{sys.getsizeof(a)}") b = set() for i in s : b.add(i) print(len(b),f"b 的內存佔用{sys.getsizeof(b)}") c = dict() for i in s : c[i] = i print(len(c) , f"c 的內存佔用{sys.getsizeof(c)}")
咱們給三類循環添加了26個元素,結果如何呢?
由此能看出可變對象在擴充時的祕密:
以上的可變對象在擴充時,有類似的分配機制,在動態擴容時可明顯看出效果。
那麼,靜態建立的對象是否也有這樣的分配機制呢?它跟動態擴容比,是否有所區別呢?
先看看集合與字典:
# 靜態建立對象 set_1 = {1, 2, 3, 4} set_2 = {1, 2, 3, 4, 5} dict_1 = {'a':1, 'b':2, 'c':3, 'd':4, 'e':5} dict_2 = {'a':1, 'b':2, 'c':3, 'd':4, 'e':5, 'f':6} sys.getsizeof(set_1) # 224 sys.getsizeof(set_2) # 736 sys.getsizeof(dict_1) # 240 sys.getsizeof(dict_2) # 368
看到這個結果,再對比上一節的截圖,能夠看出:在元素個數相等時,靜態建立的集合/字典所佔的內存跟動態擴容時徹底同樣。
這個結論是否適用於列表對象呢?一塊兒看看:
list_1 = ['a', 'b'] list_2 = ['a', 'b', 'c'] list_3 = ['a', 'b', 'c', 'd'] list_4 = ['a', 'b', 'c', 'd', 'e'] sys.getsizeof(list_1) # 80 sys.getsizeof(list_2) # 88 sys.getsizeof(list_3) # 96 sys.getsizeof(list_4) # 104
上一節的截圖顯示,列表在前 4 個元素時都佔 96 字節,在 5 個元素時佔 128 字節,與這裏明顯矛盾。
因此,這個祕密昭然若揭:在元素個數相等時,靜態建立的列表所佔的內存有可能小於動態擴容時的內存!
也就是說,這兩種列表看似相同,實際卻不一樣!列表不等於列表!
EMMM.....Interesting......
前面提到了,擴充可變對象時,可能會申請新的內存。
那麼,若是反過來縮減可變對象,減掉一些元素後,新申請的內存是否會自動回收掉呢?
import sys a = [1, 2, 3, 4] sys.getsizeof(a) # 初始值:96 a.append(5) # 擴充後:[1, 2, 3, 4, 5] sys.getsizeof(a) # 擴充後:128 a.pop() # 縮減後:[1, 2, 3, 4] sys.getsizeof(a) # 縮減後:128
如代碼所示,列表在一擴一縮後,雖然回到了原樣,可是所佔用的內存空間可沒有自動釋放啊。其它的可變對象同理。
這就是 Python 的小祕密了,「胖子沒法減重原理」:瘦子變胖容易,縮減身型也容易,可是體重減不掉,哈哈~~~
使用 pop() 方法,只會縮減可變對象中的元素,但並不會釋放已申請的內存空間。
還有個 clear() 方法,它會清空可變對象的全部元素,讓咱們試試看吧:
import sys a = [1, 2, 3] b = {1, 2, 3} c = {'a':1, 'b':2, 'c':3} sys.getsizeof(a) # 88 sys.getsizeof(b) # 224 sys.getsizeof(c) # 240 a.clear() # 清空後:[] b.clear() # 清空後:set() c.clear() # 清空後:{},也即 dict()
調用 clear() 方法,咱們就得到了幾個空對象。
在第一小節裏,它們的內存大小已經被查驗過了。(前面說過會考的,請默寫下)
可是,若是這時再查驗的話,你會驚訝地發現,這些空對象的大小跟前面查的並不徹底同樣!
# 承接前面的清空操做: sys.getsizeof(a) # 64 sys.getsizeof(b) # 224 sys.getsizeof(c) # 72
空列表與空元組的大小不變,然而空字典(72)居然比前面的空字典(240)要小不少!
也就是說,列表與元組在清空元素後,回到起點不變初心,然而,字典這傢伙倒是「賠了夫人又折兵」,不只把「吃」進去的全吐出來了,還把本身的老本給虧掉了!
EMMM.....Interesting......
以上就是 Python 在分配內存時的幾個小祕密啦,看完以後,又能夠出去裝逼了呢!
若有心得或者疑問,歡迎留言一塊兒交流哦~