Python tricks(7) -- new-style class的__slots__屬性

__slots__是在python 2.2開始引入的一個新特性, 咱們來看一下官方給出的解釋.html

This class variable can be assigned a string, iterable, or sequence of strings with variable names used by instances. If defined in a new-style class, __slots__ reserves space for the declared variables and prevents the automatic creation of __dict__ and__weakref__ for each instance.python

爲聲明的字段保留空間, 再也不爲每一個實例生成一個__dict__和__weakref__. [參考文獻1]app

python默認定義的class是能夠動態添加屬性的, 代碼示例以下源碼分析

In [1]: class Test(object):
   ...:     pass
   ...:

In [2]: a = Test()

In [3]: a.a = 1

In [4]: a.a
Out[4]: 1

能夠動態地爲a添加一個屬性, 在class中會默認建立一個特殊的對象__dict__, 負責保存class的全部屬性和方法.優化

這個__slots__主要的一個做用是減小了對內存的消耗, 這個尤爲在對象數量較多的時候很是管用, 能夠查看[參考文獻3], 使用__slots__未做任何其餘優化, 節省了9G內存.spa

示例代碼以下htm

import sys
from guppy import hpy


class Person_(object):
    __slots__ = ("name", "age", "gender")

    def __init__(self):
        pass


class Person(object):
    def __init__(self):
        pass


if __name__ == "__main__":
    persons = []
    for i in xrange(100000):
        p = Person()
        p.name = "name_%d" % i
        p.age = i
        p.gender = "female"
        persons.append(p)

    persons_ = []
    for i in xrange(100000):
        p = Person_()
        p.name = "name_%d" % i
        p.age = i
        p.gender = "female"
        persons_.append(p)

    print "size without slots: %d" % sum([sys.getsizeof(p) for p in persons])
    print "size of the __dict__ without slots: %d" % sum([sys.getsizeof(p.__dict__) for p in persons])
    print "size of the __weakref__ without slots: %d" % sum([sys.getsizeof(p.__weakref__) for p in persons])
    print "size with slots: %d" % sum([sys.getsizeof(p) for p in persons_])

    h = hpy()
    print h.heap()

程序輸出結果以下:對象

size without slots: 3200000
size of the __dict__ without slots: 14000000
size of the __weakref__ without slots: 800000
size with slots: 3600000
Partition of a set of 739737 objects. Total size = 32889732 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0 100000  14 14000000  43  14000000  43 dict of __main__.Person
     1 214673  29  7335252  22  21335252  65 str
     2 100000  14  3600000  11  24935252  76 __main__.Person_
     3 100000  14  3200000  10  28135252  86 __main__.Person
     4 209137  28  2509644   8  30644896  93 int
     5    181   0   831828   3  31476724  96 list
     6   7627   1   339356   1  31816080  97 tuple
     7    331   0   232004   1  32048084  97 dict (no owner)
     8   1670   0   120240   0  32168324  98 types.CodeType
     9     73   0   110060   0  32278384  98 dict of module
<95 more rows. Type e.g. '_.more' to view.>

 

獲取內存大小的兩種方法: 一種使用sys.getsizeof()方法, 一種能夠採用第三方庫guppy的hpy來查看, 上面的示例代碼同時使用了這兩種方式blog

Person是默認的new-style class, Person_是帶有slots的new-style class. 爲兩者建立100000個對象, 來查看內存大小.內存

直接算Person_的大小總共爲3.6M左右, Person的大小爲3.2M. 我一直很奇怪這個爲何反而大了呢?

後來發現貌似__dict__的大小沒有被算進去, Person.__dict__的總大小爲14M, 這樣Person的總大小爲17.2M, 而Person_沒有__dict__屬性, 總大小爲3.6M, 由此能夠看出, slots能夠節省很是多的內存. 

__dict__通常的實現都是用空間換取時間, 因此自己的內存消耗很是大, 在對象數量越多的時候越明顯.

你們若是在項目中發現python佔用很大內存的時候, 能夠考慮從這個角度去進行內存優化, 大部分狀況是能夠取得不錯的效果.

水平有限, 歡迎拍磚!

 

參考文獻

  1. 官方文檔: http://docs.python.org/2.7/reference/datamodel.html?highlight=slots#__slots__
  2. slots的源碼分析: http://www.kvmapp.com/program/python/python-slots.html
  3. 節省內存的實例: http://tech.oyster.com/save-ram-with-python-slots/
相關文章
相關標籤/搜索