就像其他人提到的那样,定义的唯一原因__slots__
是当您拥有带有预定义属性集的简单对象并且不希望每个对象都携带字典时节省一些内存。当然,这仅对您计划拥有多个实例的类有意义。
节省可能不会立即显而易见-考虑...:
>>> class NoSlots(object): pass
...
>>> n = NoSlots()
>>> class WithSlots(object): __slots__ = 'a', 'b', 'c'
...
>>> w = WithSlots()
>>> n.a = n.b = n.c = 23
>>> w.a = w.b = w.c = 23
>>> sys.getsizeof(n)
32
>>> sys.getsizeof(w)
36
由此看来,这似乎与该型槽尺寸更大而不是无缝隙的大小!但这是一个错误,因为sys.getsizeof
不考虑字典等“对象内容”:
>>> sys.getsizeof(n.__dict__)
140
由于字典本身仅占用140个字节,因此显然n
声称“ 32字节”对象并未考虑每个实例涉及的所有内容。您可以使用pympler等第三方扩展来做得更好:
>>> import pympler.asizeof
>>> pympler.asizeof.asizeof(w)
96
>>> pympler.asizeof.asizeof(n)
288
这可以更清楚地显示出节省的内存空间__slots__
:对于这样的简单对象,它不到200个字节,几乎是对象总空间的2/3。现在,由于这些天或多或少的兆字节对大多数应用程序来说并没有多大关系,所以这也告诉您,__slots__
如果一次仅要拥有数千个实例,那是不值得的。但是,对于数百万个实例,它确实的确具有非常重要的意义。您还可以实现微观上的加速(部分原因是通过使用可以更好地对小型对象使用缓存__slots__
):
$ python -mtimeit -s'class S(object): __slots__="x","y"' -s's=S(); s.x=s.y=23' 's.x'
10000000 loops, best of 3: 0.37 usec per loop
$ python -mtimeit -s'class S(object): pass' -s's=S(); s.x=s.y=23' 's.x'
1000000 loops, best of 3: 0.604 usec per loop
$ python -mtimeit -s'class S(object): __slots__="x","y"' -s's=S(); s.x=s.y=23' 's.x=45'
1000000 loops, best of 3: 0.28 usec per loop
$ python -mtimeit -s'class S(object): pass' -s's=S(); s.x=s.y=23' 's.x=45'
1000000 loops, best of 3: 0.332 usec per loop
但是这一定程度上取决于Python版本(这些都是我用2.5重复测量号;用2.6,我看到一个较大的相对优势,__slots__
对设置的属性,但根本没有,确实是一个微小的DIS优势,为得到它)。
现在,关于继承:为了使实例无字典,其继承链中的所有类也必须具有无字典的实例。具有无dict实例的类是那些定义__slots__
,加上大多数内置类型的类(其实例具有dict的内置类型是可以在其实例上设置任意属性(例如函数)的那些类型)。插槽名称中的重叠不被禁止,但是它们是无用的,并且浪费了一些内存,因为插槽是继承的:
>>> class A(object): __slots__='a'
...
>>> class AB(A): __slots__='b'
...
>>> ab=AB()
>>> ab.a = ab.b = 23
>>>
如您所见,您可以a
在AB
实例上设置属性-AB
本身仅定义slot b
,但它继承a
自A
。禁止重复继承的插槽:
>>> class ABRed(A): __slots__='a','b'
...
>>> abr=ABRed()
>>> abr.a = abr.b = 23
但确实浪费了一点内存:
>>> pympler.asizeof.asizeof(ab)
88
>>> pympler.asizeof.asizeof(abr)
96
因此,实际上没有理由这样做。