默認情況下,python對象隊象的每個實例(instance)都會有一個字典來存儲該實例的屬性,這樣做的好處在於運行時期每個對象可以任意設置新的屬性。而相對應的壞處是,當創建成百上千個這樣的實例的時候回很浪費內存。所以引入__slots__
,用來指定實例只擁有固定的屬性,因此python會給每個實例對象分配固定的內存空間,從而減少內存消耗。而且使用__slots__
可以加快屬性的訪問。
__slots__
可以被設置成屬性名稱的字符串,可遍歷的對象或者序列。
之前在看odoo源碼緩存相關的內容時,看到過下面這個例子:
class ormcache_counter(object):
""" Statistic counters for cache entries. """
__slots__ = ['hit', 'miss', 'err']
def __init__(self):
self.hit = 0
self.miss = 0
self.err = 0
@property
def ratio(self):
return 100.0 * self.hit / (self.hit + self.miss or 1)
這裡創建了一個用來記錄每個方法緩存情況的對象,因為對於需要每個緩存的方法,都會創建一個該實例來記錄緩存的狀況(比如緩存用到或沒用的次數等),所以為了節省內存加快訪問速度這裡指定了該對象擁有的三個屬性。
timeit是python一個用來簡單測試運行時間的模塊,詳細可參見官方文檔。
# In Python2.7
# test1.py
import timeit
class Foo(object): __slots__ = 'foo',
class Bar(object): pass
slotted = Foo()
not_slotted = Bar()
def get_set_delete_fn(obj):
def get_set_delete():
obj.foo = 'foo'
obj.foo
del obj.foo
return get_set_delete
# In REPL
>>> from test1 import *
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.24305510520935059
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
min(timeit.repeat(get_set_delete_fn(slotted)))
0.21287798881530762
可以看見,使用__slots__
的對象有更快的訪問速度,雖然在python2.7中差別沒有在python3中那麼明顯
關於內存占用情況的測試我還沒測,但可以參考 stackoverflow上的測試,我這裡機(無)智(恥)地取個結果:
# 單位 bytes
attrs __slots__ no slots declared + __dict__
none 16 64 (+ 280 if __dict__ referenced)
one 56 64 + 280
two 64 64 + 280
six 96 64 + 1048
22 224 64 + 3352
可以明顯看到內存占用減少的情況。
__dict__
可以理解成類裡面存儲屬性的字典,
__slots__
的類B時,A是有__dict__
屬性,這是再定義__slots__
屬性沒有意義, 不能達到限制內存的作用__slots__
的類,而沒有定義__dict__
的類設置不在__slots__
指定的那些屬性時,會導致一個AttributeError