python 特性slots槽詳解


槽(slots)可以使用__slots_屬性來為自定的類設置以一個靜態屬性列表,並在類的每個實例中跳過__dict__字典的創建過程,可以提高訪問速度,節省內存消耗

由於Python是動態語言,任何實例在運行期都可以動態地添加屬性。
如果要限制添加的屬性,例如,Student類只允許添加 name、gender和score 這3個屬性,就可以利用Python的一個特殊的__slots__來實現

class Student(object):
    __slots__ = ('name', 'gender', 'score')
    def __init__(self, name, gender, score):
        self.name = name
        self.gender = gender
        self.score = score

  創建實例,並向其中新增屬性

>>> s = Student('Bob', 'male', 59)
>>> s.name = 'Tim' # OK
>>> s.score = 99 # OK
>>> s.grade = 'A'

  新增屬性grade執行后,拋異常【AttributeError: 'student' object has no attribute 'gride'

 當一個Student類定義了__slots__ = ('name', 'age')Studen.name就是一個有__get____set__方法的member_descriptor
__slots__的目的是限制當前類所能擁有的屬性,如果不需要添加任意動態的屬性,使用__slots__也能節省內存。

__slots__兩大特性

更快的屬性訪問速度

默認情況下,訪問一個實例的屬性是通過訪問該實例的__dict__來實現的。如訪問s.name就相當於訪問s.__dict__['name']。為了便於理解,我粗略地將它拆分為四步:s.name  >> s.__dict__ >>. s.__dict__['name'] >>. 結果

__slots__的實現可以得知,定義了__slots__的類會為每個屬性創建一個描述器。訪問屬性時就直接調用這個描述器。在這里我將它拆分為三步:b.x >>. member decriptor >>. 結果

我在上文提到,訪問__dict__和描述器的速度是相近的,而通過__dict__訪問屬性多了s.__dict__['name']字典訪值一步(一個哈希函數的消耗)。由此可以推斷出,使用了__slots__的類的屬性訪問速度比沒有使用的要快。下面用一個例子驗證:

from timeit import repeat

class A(object): pass

class B(object): __slots__ = ('x')

def get_set_del_fn(obj):
    def get_set_del():
        obj.x = 1
        obj.x
        del obj.x
    return get_set_del

a = A()
b = B()
ta = min(repeat(get_set_del_fn(a)))
tb = min(repeat(get_set_del_fn(b)))
print("%.2f%%" % ((ta/tb - 1)*100))

  在本人電腦上測試速度有0-25%左右的提升。

減少內存消耗

python內置的字典本質是一個哈希表,它是一種用空間換時間的數據結構。為了解決沖突的問題,當字典使用量超過2/3時,Python會根據情況進行2-4倍的擴容。由此可預見,取消__dict__的使用可以大幅減少實例的空間消耗。

關於slots的繼承問題

在一般情況下,使用slots的類需要直接繼承object,如class Foo(object): __slots__ = ()

在繼承自己創建的類時,我根據子類父類是否定義了__slots__,將它細分為三種情況:(暫未列出多父類的情況)

  1. 父類有,子類沒有:子類的實例還是會自動創建__dict__來存儲屬性,不過父類__slots__已有的屬性不受影響。
  2. 父類沒有,子類有:雖然子類取消了__dict__,但繼承父類后它會繼續生成。同上面一樣,__slots__已有的屬性不受影響。
  3. 父類有,子類有:只有子類的__slots__有效,訪問父類有子類沒有的屬性依然會報錯

因此:為了正確使用__slots__,最好直接繼承object。如有需要用到其他父類,則父類和子類都要定義slots,還要記得子類的slots會覆蓋父類的slots。
除非所有父類的slots都為空,否則不要使用多繼承。

在特殊情況下,可以在__slots__里添加__dict__來獲取與普通實例同樣的動態特性。
當一個類需要創建大量實例時,可以使用__slots__來減少內存消耗。如果對訪問屬性的速度有要求,也可以酌情使用。另外可以利用slots的特性來限制實例的屬性。而用在普通類身上時,使用__slots__后會喪失動態添加屬性和弱引用的功能,進而引起其他錯誤,所以在一般情況下不要使用它





免責聲明!

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



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