__slots__


如果你想要控制可以指定給物件的特性名稱,則可以在類別上定義__slots__特性,該特性是一個字串清單,列出可指定給物件的特性名稱。例如:
>>> class Some:
...     __slots__ = ['a', 'b']
...
>>> Some.__dict__.keys()
['a', '__module__', 'b', '__slots__', '__doc__']
>>> s = Some()
>>> s.a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: a
>>> s.a = 10
>>> s.a
10
>>> s.b = 20
>>> s.b
20
>>> s.c = 30

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Some' object has no attribute 'c'
>>>


如上所示,雖然在__slots__中列出的特性,就存在於類別的__dict__中,但在指定特性給實例之前,你不可以直接存取該特性,而且只有在__slots__中列出的特性,才可以被指定給實例。

如果類別具有__slots__,則該類別的實例不會具有__dict__特性。例如:
>>> s = Some()
>>> s.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Some' object has no attribute '__dict__'
>>>


實 際上,你可以在__slots__中包括'__dict__'名稱,讓實例擁有__dict__特性,但要注意,在__slots__中列出的特性名稱, 並不會存在於實例的__dict__之中。如果你要列出實例的所有特性,也必須注意是否同時包括__dict__與__slots__中列出的特性。例 如:
>>> class Some:
...     __slots__ = ['a', 'b', '__dict__']
...
>>> s = Some()
>>> s.__dict__
{}
>>> s.a = 10
>>> s.b = 20
>>> s.c = 30
>>> s.__dict__
{'c': 30}
>>> for attr in list(s.__dict__) + s.__slots__:
...     print(attr, getattr(s, attr))
...
c 30
a 10
b 20
__dict__ {'c': 30}
>>>


實際上,__slots__中的特性,Python會將之實作為 描述器。例如:
>>> class Some:
...     __slots__ = ['a', 'b']
...
>>> Some.__dict__.keys()
['a', '__module__', 'b', '__slots__', '__doc__']
>>> s = Some()
>>> Some.__dict__['a'].__set__(s, 10)
>>> Some.__dict__['a'].__get__(s, Some)
10
>>>


所以,__slots__特性最好被作為類別特性來使用,尤其是在繼承關係發生時,像是父類別中定義的__slots__,僅可以透過父類別來取得,而子類別的__slots__則僅可以透過子類別來取得
>>> class P:
...     __slots__ = ['a', 'b']
...
>>> class C(P):
...     __slots__ = ['c']
...
>>> P.__slots__
['a', 'b']
>>> C.__slots__
['c']
>>> o = C()
>>> o.c = 10
>>> o.c
10
>>> o.a = 10
>>> o.a
10
>>> o.__slots__
['c']
>>>


在尋找實例上可設定的特性時,基本上會對照父類別與子類別中的__slots__清單。然而要注意的是,由於僅有定義__slots__的類別,其實例才不會有__dict__特性,所以若父類別中沒有定義__slots__,則父類別實例的__dict__仍為可用,這會使得子類別即使定義了__slots__,也失去其意義。
>>> class P:
...     pass
...
>>> class C(P):
...     __slots__ = ['c']
...
>>> o = C()
>>> o.a = 10
>>> o.b = 10
>>> o.c = 10
>>> o.__dict__
{'a': 10, 'b': 10}
>>>


反之亦然,如果子類別沒有定義自己的__slots__,其實例也會有__dict__。例如:
>>> class P:
...     __slots__ = ['a', 'b']
...
>>> class C(P):
...     pass
...
>>> o1 = P()
>>> o1.a = 10
>>> o1.b = 10
>>> o1.c = 10
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'P' object has no attribute 'c'
>>> c = C()
>>> o2 = C()
>>> o2.a = 10
>>> o2.b = 10
>>> o2.c = 10
>>> o2.__dict__
{'c': 10}
>>>


__slots__ 中的特性是由描述器來實作,對於一些特性很少的物件來說,使用__slots__可以增加一些效能,因為__dict__是字典物件,如果物件建立後僅設 定很少的特性,對於空間是種浪費,若__slots__中使用List實作特性的存取,可以對效能有所裨益。