Python - __slots__屬性詳解

johnychen發表於2021-09-09

簡介

__slots__允許我們宣告並限定類成員,並拒絕類建立__dict____weakref__屬性以節約記憶體空間

Python是動態語言,對於普通的類,可以為類例項賦值任何屬性,這些屬性會儲存在__dict__中:

>>> class Student(object):...     pass...     >>> Abey = Student()>>> Abey.name = 'Abey'>>> Abey.__dict__
{'name': 'Abey'}

這樣的特性帶來兩個問題:

  • 資料透過字典(Hash)儲存所佔用的空間較大

  • 如何禁止隨意生成類屬性

當然,__slots__就能解決這兩個問題。透過__slots__屬性限定類屬性的建立:

>>> class Student(object):...     __slots__ = ('name', 'age')
...     
>>> Abey = Student()>>> Abey.name = 'Abey'>>> Abey.gender = 'Female'Traceback (most recent call last):
  File "<input>", line 1, in <module>AttributeError: 'Student' object has no attribute 'gender'>>> Abey.__dict__Traceback (most recent call last):
  File "<input>", line 1, in <module>AttributeError: 'Student' object has no attribute '__dict__'

可以看到,在定義了__slots__變數後,Student類例項已經不能隨意建立不在__slots__定義內的屬性gender,同時例項中也不再有__dict__結構。

用法

繼承樹

__slots__在繼承中有兩種表現:

  • 子類未宣告__slots__時,不繼承父類的__slots__,即此時子類例項可以隨意賦值屬性

  • 子類宣告__slots__時,繼承父類的__slots__,即此時子類的__slots__為其自身+父類的__slots__

以下面的父類為例:

>>> class Student(object):...     __slots__ = ('name', 'age')...

建立一個子類不宣告__slots__,該類例項可以建立父類__slots__限定之外的屬性gender

>>> class SubStudent(Student):...     pass... >>> Bob = SubStudent()>>> Bob.gender = 'Male'>>> Bob.__dict__
{'gender': 'Male'}

而建立一個宣告__slots__的子類,該類屬性則只能建立父類__slots__+自身__slots__限定的屬性:

>>> class SubStudent2(Student):...     __slots__ = 'gender'...     
>>> Cathy = SubStudent2()>>> Cathy.gender = 'Female'>>> Cathy.name = 'Cathy'>>> Cathy.teacher = 'Mrs. Wang'Traceback (most recent call last):
  File "<input>", line 1, in <module>AttributeError: 'SubStudent2' object has no attribute 'teacher'

注意:子類的__slots__本身已經繼承自父類,無需重複宣告父類已宣告的屬性。例如上例,重複宣告會多佔用記憶體空間:

>>> class SubStudent3(Student):...     __slots__ = ('name', 'age', 'gender')...    >>> from sys import getsizeof>>> getsizeof(Student()), getsizeof(SubStudent2()), getsizeof(SubStudent3())
(56, 64, 80)

效能對比

我們為什麼要使用__slots__呢?

更快速地賦值屬性

參考Stack Overflow回答中給出的資料:

import timeitclass Foo(object): __slots__ = 'foo',class Bar(object): passslotted = 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

得到測試結果為:

>>> min(timeit.repeat(get_set_delete_fn(slotted)))0.2846834529991611>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))0.3664822799983085

可以看到,在相同的環境(Ubuntu)下,slots為Python3.5帶來了接近30%的賦值速度提升:

>>> 0.3664822799983085 / 0.28468345299916111.2873325658284342

節約記憶體空間

由於不使用__dict__儲存物件的屬性,__slots__在一些場景下能夠節約極大的記憶體空間。具體資料可以檢視參考中的回答連結,不贅述。



作者:嚴北
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4301/viewspace-2818822/,如需轉載,請註明出處,否則將追究法律責任。

相關文章