slots - Python的結構體 轉


 

 

 
 
上個月看了篇文章 “SAVING 9 GB OF RAM WITH PYTHON’S __SLOTS__”,原來Python也有類似結構體的東東。拖了一個月才寫這篇,是因為太久沒看python源碼而生疏了,中間又搗鼓了一下tmux神馬的。
簡單的說,slots提供了一種強制聲明對象屬性的方法。如果在類定義的時候定義了__slots__的值(string列表),這個類的對象就只能使用列表中屬性名。

class A(object):
     def __init__(self):
          self.uuid = 2
          self.word = ‘hello'

變成:

class B(object):
     __slots__ = ( 'value', ‘other')
     def __init__(self):
          self.uuid = 2
          self.word = ‘hello'
          self.other = ‘world'  # AttributeError: ‘B’ object has no attribute ‘other’

是不是很像C的結構體?注意__slots__只是起了限定的作用,屬性還是要先賦值才讀取。

引入slots的主要目的是節省內存。默認python會為每個對象創建一個dict,通過__dict__索引。腳本中定義的屬性其實都存在這個dict中。如果類定義了__slots__,創建對象時就不會創建dict,這也就解釋了為什么不能隨意增加屬性了。

能省多少內存呢?dict對象本身只有248bytes。而dict的容量通常是其中有效entry的2~4倍,使用slots不會減少有效entry的數量,因此大概能省一半的空entry。原文中能從25G內存中省下9G,應該是不僅對象很多,而且每個對象的屬性很多。考慮到我們實際的應用環境,似乎達不到這樣的量。而且服務端很多類都是在C中定義的,其實效果是一樣的。

性能上會有提高嗎?實測結果幾乎沒有,從實現上可以看出,其實還是需要做一次dict查找。

目前想到的一個用處是提高可維護性:雖然作為動態語言,允許隨意增刪對象的屬性是個很方便的設定。但是也會對可維護性造成一定的影響。比如一不小心寫錯屬性名。如果是賦值,python會默默的以為是一個新的,導致真正要改的數據沒改到,如果是讀取,一直到運行時跑到這段代碼才會報錯。通過定義slots,可以一定程度上避免錯誤賦值,但是對於錯誤讀取就作用不大了。

關於slots的使用有一些規則和限制,在python自帶的文檔里有詳細說明。

最后是實現的一些細節。代碼主要在Objects/typeobject.c。下面兩張圖是普通類和使用slots的類的區別(紅色部分)。
slots - Python的結構體 - smallka - Doh!
 對於普通類A,我們可以看到,它的tp_dictoffset的值是一個偏移量,對應於這個類所創建的對象(比如a)內存中的從頭部數到這個偏移量的內存地址,其實是一個PyObject指針。這個指針指向一個dict對象,也就是我們在運行期可以訪問到的a.__dict__。腳本中定義的所有屬性就保存在這個dict當中。由於這兩個指針分別有8字節,因此整個對象a的大小是基本大小16加上8*2,總共32字節。
slots - Python的結構體 - smallka - Doh!
 而對於使用了slots的類B,它的tp_dictoffset就變成了0。換句話說就是沒有分配dict對象。那怎么索引到屬性呢?其實是上升到了類的層次。在Python中,所有東西都是對象,包括類型本身也是對象,類型對象的類型是PyType_Type(腳本中的type)。聽起來很繞,其實這里只是想說明,類型對象是變長對象,包括基本信息加上若干PyMemberDef對象。每個PyMemberDef對象其實就對應於slots中個每個屬性聲明。而這個類所創建的對象中,也會為每個屬性聲明分配一個PyObject指針的內存。在初始化類型的時候(PyType_Ready函數),系統會為每個PyMemberDef創建一個descriptor,並在類的__dict__(不是對象的__dict__,從類的tp_dict指向)中建立從屬性名到descriptor的映射。

因此,無論哪種方法,都需要進行一次hash查找,區別只是在類的__dict__還是對象的__dict__中。

最后的最后,推薦一下這個不需要安裝的在線畫圖工具:lucidchart
 
 
 
 
 
閱讀(755)評論(0)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM