简介
__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 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
得到测试结果为:
>>> 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.2846834529991611
1.2873325658284342
节约内存空间
由于不使用__dict__
存储对象的属性,__slots__
在一些场景下能够节约极大的内存空间。具体数据可以查看参考中的回答链接,不赘述。
参考
[1] Usage of __slots__?, Aaron Hall, Stack Overflow
推荐阅读
[1] Data model, Python Document
[2] python __slots__ 使你的代码更加节省内存, david_bj, 51CTO
[3] 使用__slots__, 廖雪峰