為什么有__slots__
屬性?
默認情況下红且,python對象隊象的每個實例(instance)都會有一個字典來存儲該實例的屬性坝茎,這樣做的好處在于運行時期每個對象可以任意設置新的屬性。而相對應的壞處是暇番,當創(chuàng)建成百上千個這樣的實例的時候回很浪費內(nèi)存嗤放。所以引入__slots__
,用來指定實例只擁有固定的屬性壁酬,因此python會給每個實例對象分配固定的內(nèi)存空間次酌,從而減少內(nèi)存消耗恨课。而且使用__slots__
可以加快屬性的訪問。
用法
__slots__
可以被設置成屬性名稱的字符串岳服,可遍歷的對象或者序列剂公。
之前在看odoo源碼緩存相關的內(nèi)容時,看到過下面這個例子:
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)
這里創(chuàng)建了一個用來記錄每個方法緩存情況的對象吊宋,因為對于需要每個緩存的方法纲辽,都會創(chuàng)建一個該實例來記錄緩存的狀況(比如緩存用到或沒用的次數(shù)等),所以為了節(jié)省內(nèi)存加快訪問速度這里指定了該對象擁有的三個屬性璃搜。
測試
訪問速度測試
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中那么明顯
內(nèi)存占用參考
關于內(nèi)存占用情況的測試我還沒測,但可以參考 stackoverflow上的測試,我這里機(無)智(恥)地取個結(jié)果:
# 單位 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
可以明顯看到內(nèi)存占用減少的情況翘地。
注意事項
__dict__
可以理解成類里面存儲屬性的字典堡距,
- 當一個類A繼承自一個沒有定義
__slots__
的類B時,A是有__dict__
屬性拒名,這是再定義__slots__
屬性沒有意義, 不能達到限制內(nèi)存的作用 - 當嘗試給一個定義了
__slots__
的類芋酌,而沒有定義__dict__
的類設置不在__slots__
指定的那些屬性時增显,會導致一個AttributeError
其它注意請參照文檔