
作者:豌豆花下貓
來(lái)源:Python貓
我之前的一篇文章,帶大家揭曉了python 在給內(nèi)置對(duì)象分配內(nèi)存時(shí)的 5 個(gè)奇怪而有趣的小秘密。文中使用了sys.getsizeof()來(lái)計(jì)算內(nèi)存,但是用這個(gè)方法計(jì)算時(shí),可能會(huì)出現(xiàn)意料不到的問(wèn)題。
文檔中關(guān)于這個(gè)方法的介紹有兩層意思:
也就是說(shuō),getsizeof() 并不是計(jì)算實(shí)際對(duì)象的字節(jié)大小,而是計(jì)算“占位對(duì)象”的大小。如果你想計(jì)算所有屬性以及屬性的屬性的大小,getsizeof() 只會(huì)停留在第一層,這對(duì)于存在引用的對(duì)象,計(jì)算時(shí)就不準(zhǔn)確。
例如列表 [1,2],getsizeof() 不會(huì)把列表內(nèi)兩個(gè)元素的實(shí)際大小算上,而只是計(jì)算了對(duì)它們的引用。舉一個(gè)形象的例子,我們把列表想象成一個(gè)箱子,把它存儲(chǔ)的對(duì)象想象成一個(gè)個(gè)球,現(xiàn)在箱子里有兩張紙條,寫(xiě)上了球 1 和球 2 的地址(球不在箱子里),getsizeof() 只是把整個(gè)箱子稱(chēng)重(含紙條),而沒(méi)有根據(jù)紙條上地址,找到兩個(gè)球一起稱(chēng)重。
1、計(jì)算的是什么?
我們先來(lái)看看列表對(duì)象的情況:
如圖所示,單獨(dú)計(jì)算 a 和 b 列表的結(jié)果是 36 和 48,然后把它們作為 c 列表的子元素時(shí),該列表的計(jì)算結(jié)果卻僅僅才 36。(PS:我用的是 32 位解釋器)
如果不使用引用方式,而是直接把子列表寫(xiě)進(jìn)去,例如 “d = [[1,2],[1,2,3,4,5]]”,這樣計(jì)算 d 列表的結(jié)果也還是 36,因?yàn)樽恿斜硎仟?dú)立的對(duì)象,在 d 列表中存儲(chǔ)的是它們的 id。
也就是說(shuō):getsizeof() 方法在計(jì)算列表大小時(shí),其結(jié)果跟元素個(gè)數(shù)相關(guān),但跟元素本身的大小無(wú)關(guān)。
下面再看看字典的例子:
明顯可以看出,三個(gè)字典實(shí)際占用的全部?jī)?nèi)存不可能相等,但是 getsizeof() 方法給出的結(jié)果卻相同,這意味著它只關(guān)心鍵的數(shù)量,而不關(guān)心實(shí)際的鍵值對(duì)是什么內(nèi)容,情況跟列表相似。
2、“淺計(jì)算”與其它問(wèn)題
有個(gè)概念叫“淺拷貝”,指的是 copy() 方法只拷貝引用對(duì)象的內(nèi)存地址,而非實(shí)際的引用對(duì)象。類(lèi)比于這個(gè)概念,我們可以認(rèn)為 getsizeof() 是一種“淺計(jì)算”。
“淺計(jì)算”不關(guān)心真實(shí)的對(duì)象,所以其計(jì)算結(jié)果只是一個(gè)假象。這是一個(gè)值得注意的問(wèn)題,但是注意到這點(diǎn)還不夠,我們還可以發(fā)散地思考如下的問(wèn)題:
關(guān)于第一個(gè)問(wèn)題,getsizeof(x) 方法實(shí)際會(huì)調(diào)用 x 對(duì)象的__sizeof__() 魔術(shù)方法,對(duì)于內(nèi)置對(duì)象來(lái)說(shuō),這個(gè)方法是通過(guò) CPython 解釋器實(shí)現(xiàn)的。
我查到這篇文章《Python中對(duì)象的內(nèi)存使用(一)》,它分析了 CPython 源碼,最終定位到的核心代碼是這一段:
我看不懂這段代碼,但是可以知道的是,它在計(jì)算 Python 對(duì)象的大小時(shí),只跟該對(duì)象的結(jié)構(gòu)體的屬性相關(guān),而沒(méi)有進(jìn)一步作“深度計(jì)算”。
對(duì)于 CPython 的這種實(shí)現(xiàn),我們可以注意到兩個(gè)層面上的區(qū)別:
由此,我有一個(gè)不成熟的猜測(cè):基于“一切皆是對(duì)象”的設(shè)計(jì)原則,int 及其它基礎(chǔ)的 C 數(shù)據(jù)類(lèi)型在 Python 中被套上了一層“殼”,所以需要一個(gè)方法來(lái)計(jì)算它們的大小,也即是 getsizeof()。
官方文檔中說(shuō)“All built-in objects will return correct results” [1],指的應(yīng)該是數(shù)字、字符串和布爾值之類(lèi)的簡(jiǎn)單對(duì)象。但是不包括列表、元組和字典等在內(nèi)部存在引用關(guān)系的類(lèi)型。
為什么不推廣到所有內(nèi)置類(lèi)型上呢?我未查到這方面的解釋?zhuān)粲兄榈耐瑢W(xué),煩請(qǐng)告知。
3、“深計(jì)算”與其它問(wèn)題
與“淺計(jì)算”相對(duì)應(yīng),我們可以定義出一種“深計(jì)算”。對(duì)于前面的兩個(gè)例子,“深計(jì)算”應(yīng)該遍歷每個(gè)內(nèi)部元素以及可能的子元素,累加計(jì)算它們的字節(jié),最后算出總的內(nèi)存大小。
那么,我們應(yīng)該注意的問(wèn)題有:
Stackoverflow 網(wǎng)站上有個(gè)年代久遠(yuǎn)的問(wèn)題“How do I determine the size of an object in Python?” [2],實(shí)際上問(wèn)的就是如何實(shí)現(xiàn)“深計(jì)算”的問(wèn)題。
有不同的開(kāi)發(fā)者貢獻(xiàn)了兩個(gè)項(xiàng)目:pympler 和 pysize :第一個(gè)項(xiàng)目已發(fā)布在 Pypi 上,可以“pip install pympler”安裝;第二個(gè)項(xiàng)目爛尾了,作者也沒(méi)發(fā)布到 Pypi 上(注:Pypi 上已有個(gè) pysize 庫(kù),是用來(lái)做格式轉(zhuǎn)化的,不要混淆),但是可以在 Github 上獲取到其源碼。
對(duì)于前面的兩個(gè)例子,我們可以拿這兩個(gè)項(xiàng)目分別測(cè)試一下:
單看數(shù)值的話(huà),pympler 似乎確實(shí)比 getsizeof() 合理多了。再看看 pysize,直接看測(cè)試結(jié)果是(獲取其源碼過(guò)程略):
可以看出,它比 pympler 計(jì)算的結(jié)果略小。就兩個(gè)項(xiàng)目的完整度、使用量與社區(qū)貢獻(xiàn)者規(guī)模來(lái)看,pympler 的結(jié)果似乎更為可信。
那么,它們分別是怎么實(shí)現(xiàn)的呢?那微小的差異是怎么導(dǎo)致的?從它們的實(shí)現(xiàn)方案中,我們可以學(xué)習(xí)到什么呢?
pysize 項(xiàng)目很簡(jiǎn)單,只有一個(gè)核心方法:
除去判斷__dict__和 __slots__屬性的部分(針對(duì)類(lèi)對(duì)象),它主要是對(duì)字典類(lèi)型及可迭代對(duì)象(除字符串、bytes、bytearray)作遞歸的計(jì)算,邏輯并不復(fù)雜。
以 [1,2] 這個(gè)列表為例,它先用 sys.getsizeof() 算出 36 字節(jié),再計(jì)算內(nèi)部的兩個(gè)元素得 14*2=28 字節(jié),最后相加得到 64 字節(jié)。
相比之下,pympler 所考慮的內(nèi)容要多很多,入口在這:
它可以接受多個(gè)參數(shù),再用 sum() 方法合并。所以核心的計(jì)算方法其實(shí)是 _sizer()。但代碼很復(fù)雜,繞來(lái)繞去像一座迷宮:
它的核心邏輯是把每個(gè)對(duì)象的 size 分為兩部分:flat size 和 item size。計(jì)算 flat size 的邏輯在:
這里出現(xiàn)的 mask 是為了作字節(jié)對(duì)齊,默認(rèn)值是 7,該計(jì)算公式表示按 8 個(gè)字節(jié)對(duì)齊。對(duì)于 [1,2] 列表,會(huì)算出 (36+7)&~7=40 字節(jié)。同理,對(duì)于單個(gè)的 item,比如列表中的數(shù)字 1,sys.getsizeof(1) 等于 14,而 pympler 會(huì)算成對(duì)齊的數(shù)值 16,所以匯總起來(lái)是 40+16+16=72 字節(jié)。這就解釋了為什么 pympler 算的結(jié)果比 pysize 大。
字節(jié)對(duì)齊一般由具體的編譯器實(shí)現(xiàn),而且不同的編譯器還會(huì)有不同的策略,理論上 Python 不應(yīng)關(guān)心這么底層的細(xì)節(jié),內(nèi)置的 getsizeof() 方法就沒(méi)有考慮字節(jié)對(duì)齊。
在不考慮其它 edge cases 的情況下,可以認(rèn)為 pympler 是在 getsizeof() 的基礎(chǔ)上,既考慮了遍歷取引用對(duì)象的 size,又考慮到了實(shí)際存儲(chǔ)時(shí)的字節(jié)對(duì)齊問(wèn)題,所以它會(huì)顯得更加貼近現(xiàn)實(shí)。
4、小結(jié)
getsizeof() 方法的問(wèn)題是顯而易見(jiàn)的,我創(chuàng)造了一個(gè)“淺計(jì)算”概念給它。這個(gè)概念借鑒自 copy() 方法的“淺拷貝”,同時(shí)對(duì)應(yīng)于 deepcopy() “深拷貝”,我們還能推理出一個(gè)“深計(jì)算”。
前面展示了兩個(gè)試圖實(shí)現(xiàn)“深計(jì)算”的項(xiàng)目(pysize+pympler),兩者在淺計(jì)算的基礎(chǔ)上,深入地求解引用對(duì)象的大小。pympler 項(xiàng)目的完整度較高,代碼中有很多細(xì)節(jié)上的設(shè)計(jì),比如字節(jié)對(duì)齊。
Python 官方團(tuán)隊(duì)當(dāng)然也知道 getsizeof() 方法的局限性,他們甚至在文檔中加了一個(gè)鏈接 [3],指向了一份實(shí)現(xiàn)深計(jì)算的示例代碼。那份代碼比 pysize 還要簡(jiǎn)單(沒(méi)有考慮類(lèi)對(duì)象的情況)。
未來(lái) Python 中是否會(huì)出現(xiàn)深計(jì)算的方法,假設(shè)命名為 getdeepsizeof() 呢?這不得而知了。
本文的目的是加深對(duì) getsizeof() 方法的理解,區(qū)分淺計(jì)算與深計(jì)算,分析兩個(gè)深計(jì)算項(xiàng)目的實(shí)現(xiàn)思路,指出幾個(gè)值得注意的問(wèn)題。
讀完這里,希望你也能有所收獲。若有什么想法,歡迎一起交流。
想從事業(yè)務(wù)型數(shù)據(jù)分析師,您可以點(diǎn)擊>>>“數(shù)據(jù)分析師”了解課程詳情;
想從事大數(shù)據(jù)分析師,您可以點(diǎn)擊>>>“大數(shù)據(jù)就業(yè)”了解課程詳情;
想成為人工智能工程師,您可以點(diǎn)擊>>>“人工智能就業(yè)”了解課程詳情;
想了解Python數(shù)據(jù)分析,您可以點(diǎn)擊>>>“Python數(shù)據(jù)分析師”了解課程詳情;
想咨詢(xún)互聯(lián)網(wǎng)運(yùn)營(yíng),你可以點(diǎn)擊>>>“互聯(lián)網(wǎng)運(yùn)營(yíng)就業(yè)班”了解課程詳情;
數(shù)據(jù)分析咨詢(xún)請(qǐng)掃描二維碼
若不方便掃碼,搜微信號(hào):CDAshujufenxi
SQL Server 中 CONVERT 函數(shù)的日期轉(zhuǎn)換:從基礎(chǔ)用法到實(shí)戰(zhàn)優(yōu)化 在 SQL Server 的數(shù)據(jù)處理中,日期格式轉(zhuǎn)換是高頻需求 —— 無(wú)論 ...
2025-09-18MySQL 大表拆分與關(guān)聯(lián)查詢(xún)效率:打破 “拆分必慢” 的認(rèn)知誤區(qū) 在 MySQL 數(shù)據(jù)庫(kù)管理中,“大表” 始終是性能優(yōu)化繞不開(kāi)的話(huà)題。 ...
2025-09-18CDA 數(shù)據(jù)分析師:表結(jié)構(gòu)數(shù)據(jù) “獲取 - 加工 - 使用” 全流程的賦能者 表結(jié)構(gòu)數(shù)據(jù)(如數(shù)據(jù)庫(kù)表、Excel 表、CSV 文件)是企業(yè)數(shù)字 ...
2025-09-18DSGE 模型中的 Et:理性預(yù)期算子的內(nèi)涵、作用與應(yīng)用解析 動(dòng)態(tài)隨機(jī)一般均衡(Dynamic Stochastic General Equilibrium, DSGE)模 ...
2025-09-17Python 提取 TIF 中地名的完整指南 一、先明確:TIF 中的地名有哪兩種存在形式? 在開(kāi)始提取前,需先判斷 TIF 文件的類(lèi)型 —— ...
2025-09-17CDA 數(shù)據(jù)分析師:解鎖表結(jié)構(gòu)數(shù)據(jù)特征價(jià)值的專(zhuān)業(yè)核心 表結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 規(guī)范存儲(chǔ)的結(jié)構(gòu)化數(shù)據(jù),如數(shù)據(jù)庫(kù)表、Excel 表、 ...
2025-09-17Excel 導(dǎo)入數(shù)據(jù)含缺失值?詳解 dropna 函數(shù)的功能與實(shí)戰(zhàn)應(yīng)用 在用 Python(如 pandas 庫(kù))處理 Excel 數(shù)據(jù)時(shí),“缺失值” 是高頻 ...
2025-09-16深入解析卡方檢驗(yàn)與 t 檢驗(yàn):差異、適用場(chǎng)景與實(shí)踐應(yīng)用 在數(shù)據(jù)分析與統(tǒng)計(jì)學(xué)領(lǐng)域,假設(shè)檢驗(yàn)是驗(yàn)證研究假設(shè)、判斷數(shù)據(jù)差異是否 “ ...
2025-09-16CDA 數(shù)據(jù)分析師:掌控表格結(jié)構(gòu)數(shù)據(jù)全功能周期的專(zhuān)業(yè)操盤(pán)手 表格結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 存儲(chǔ)的結(jié)構(gòu)化數(shù)據(jù),如 Excel 表、數(shù)據(jù) ...
2025-09-16MySQL 執(zhí)行計(jì)劃中 rows 數(shù)量的準(zhǔn)確性解析:原理、影響因素與優(yōu)化 在 MySQL SQL 調(diào)優(yōu)中,EXPLAIN執(zhí)行計(jì)劃是核心工具,而其中的row ...
2025-09-15解析 Python 中 Response 對(duì)象的 text 與 content:區(qū)別、場(chǎng)景與實(shí)踐指南 在 Python 進(jìn)行 HTTP 網(wǎng)絡(luò)請(qǐng)求開(kāi)發(fā)時(shí)(如使用requests ...
2025-09-15CDA 數(shù)據(jù)分析師:激活表格結(jié)構(gòu)數(shù)據(jù)價(jià)值的核心操盤(pán)手 表格結(jié)構(gòu)數(shù)據(jù)(如 Excel 表格、數(shù)據(jù)庫(kù)表)是企業(yè)最基礎(chǔ)、最核心的數(shù)據(jù)形態(tài) ...
2025-09-15Python HTTP 請(qǐng)求工具對(duì)比:urllib.request 與 requests 的核心差異與選擇指南 在 Python 處理 HTTP 請(qǐng)求(如接口調(diào)用、數(shù)據(jù)爬取 ...
2025-09-12解決 pd.read_csv 讀取長(zhǎng)浮點(diǎn)數(shù)據(jù)的科學(xué)計(jì)數(shù)法問(wèn)題 為幫助 Python 數(shù)據(jù)從業(yè)者解決pd.read_csv讀取長(zhǎng)浮點(diǎn)數(shù)據(jù)時(shí)的科學(xué)計(jì)數(shù)法問(wèn)題 ...
2025-09-12CDA 數(shù)據(jù)分析師:業(yè)務(wù)數(shù)據(jù)分析步驟的落地者與價(jià)值優(yōu)化者 業(yè)務(wù)數(shù)據(jù)分析是企業(yè)解決日常運(yùn)營(yíng)問(wèn)題、提升執(zhí)行效率的核心手段,其價(jià)值 ...
2025-09-12用 SQL 驗(yàn)證業(yè)務(wù)邏輯:從規(guī)則拆解到數(shù)據(jù)把關(guān)的實(shí)戰(zhàn)指南 在業(yè)務(wù)系統(tǒng)落地過(guò)程中,“業(yè)務(wù)邏輯” 是連接 “需求設(shè)計(jì)” 與 “用戶(hù)體驗(yàn) ...
2025-09-11塔吉特百貨孕婦營(yíng)銷(xiāo)案例:數(shù)據(jù)驅(qū)動(dòng)下的精準(zhǔn)零售革命與啟示 在零售行業(yè) “流量紅利見(jiàn)頂” 的當(dāng)下,精準(zhǔn)營(yíng)銷(xiāo)成為企業(yè)突圍的核心方 ...
2025-09-11CDA 數(shù)據(jù)分析師與戰(zhàn)略 / 業(yè)務(wù)數(shù)據(jù)分析:概念辨析與協(xié)同價(jià)值 在數(shù)據(jù)驅(qū)動(dòng)決策的體系中,“戰(zhàn)略數(shù)據(jù)分析”“業(yè)務(wù)數(shù)據(jù)分析” 是企業(yè) ...
2025-09-11Excel 數(shù)據(jù)聚類(lèi)分析:從操作實(shí)踐到業(yè)務(wù)價(jià)值挖掘 在數(shù)據(jù)分析場(chǎng)景中,聚類(lèi)分析作為 “無(wú)監(jiān)督分組” 的核心工具,能從雜亂數(shù)據(jù)中挖 ...
2025-09-10統(tǒng)計(jì)模型的核心目的:從數(shù)據(jù)解讀到?jīng)Q策支撐的價(jià)值導(dǎo)向 統(tǒng)計(jì)模型作為數(shù)據(jù)分析的核心工具,并非簡(jiǎn)單的 “公式堆砌”,而是圍繞特定 ...
2025-09-10