用描述器 (Descriptor) 實作 Python 物件以節省記憶體資源
參考資料:
Dictionary Attribute
一般的自訂 Class 如下:
class A: pass a = A() a.c = 1 print(a.c) #1
其實同於:
a = A() a.__dict__['c'] = 1 print(a.__dict__['c'])
其中 a.c
和 a.__dict__['c']
的記憶體位置是一樣的。
是因為透過存取 __dict__
特性的方式,用字典類型將鍵值存在 a 物件中。
這樣子可以透過外部疊加特性 (Attribute) 至 a 物件,甚至是 function 都可以在建立 a 物件後加上,不過只有 a 物件可以使用。
Slots Attribute
Python 提供另一種方式存取 Class 的特性:
class A: __slots__ = ('b', 'c') a = A() a.b = 1 #Okay a.c = 2 #Okay print(a.c) #2 a.d = 3 ''' AttributeError: 'A' object has no attribute 'd' '''
使用 __slots__
特性時,會移除 __dict__
特性,改用描述器 (Descriptor) 來存取其他特性。
上述程式碼同下:
a = A() A.__dict__['b'].__set__(a, 1) A.__dict__['c'].__set__(a, 2) print(A.__dict__['c'].__get__(a, A))
而 A.__dict__
是屬於 mappingproxy 類型,並非一般的 dict 類型,因此無法動態加入新的成員,因此 a 物件就無法加入新特性。
>>> A.__dict__ mappingproxy({'__module__': '__main__', 'b': <member 'b' of 'A' objects>, '__doc__': None, 'c': <member 'c' of 'A' objects>, '__slots__': ('b', 'c')}) >>> a.__dict__ Traceback (innermost last): File "<stdin>", line 1, in <module> AttributeError: 'A' object has no attribute '__dict__'
使用 Descriptor 來操作類型的特性時,是使用固定記憶體位置,會比 dict 還節省資源。
因此若是只有少數 attribute 物件時,使用 __slots__
列舉,可以提昇較多效能。
這種方法類似 C# 的結構 (Structure) 比類別 (Class) 省資源的原理。
繼承
在繼承類型時,要注意幾個問題:
- 沒有定義
__slots__
時,會自動產生__dict__
特性。 - 當
__dict__
和__slots__
特性共存時,就會失去__slots__
的意義了。
由於子類型會接收父類型的特性,因此下面的情況都不盡理想:
#這樣會導致 B 類型無法使用 A 類型的特性。 class A: __slots__ = ('a',) class B(A): __slots__ = ('b',) #這樣會導致 B 類型產生 __dict__。 class A: __slots__ = ('a',) class B(A): pass #這樣會導致 B 類型繼承 A 類型的 __dict__。 class A: pass class B(A): __slots__ = ('b',)
可以使用這樣的方法解決:
class A: __slots__ = ('a',) class B(A): __slots__ = A.__slots__+('b',) b = B() b.a = 10 print(b.a) #10
PyQt 的類型由於是 wrapper 製作的,已經有空的 __dict__
特性,繼承後使用此方法就沒意義了。
Comments
comments powered by Disqus