PEP 412,在Python 3.3中实现,引入了对属性字典的改进处理,有效地减少了类实例的内存占用。 __slots__
是出于相同目的而设计的,因此使用__slots__
是否还有任何意义?
为了试图自己找出答案,我进行了以下测试,但结果没有太大意义:
__slots__
Python 3.3结果:
class Slots(object):
__slots__ = ['a', 'b', 'c', 'd', 'e']
def __init__(self):
self.a = 1
self.b = 1
self.c = 1
self.d = 1
self.e = 1
class NoSlots(object):
def __init__(self):
self.a = 1
self.b = 1
self.c = 1
self.d = 1
self.e = 1
Python 2.7结果:
>>> sys.getsizeof([Slots() for i in range(1000)])
Out[1]: 9024
>>> sys.getsizeof([NoSlots() for i in range(1000)])
Out[1]: 9024
我原本希望大小至少在Python 2.7上有所不同,所以我认为测试存在问题。
否,PEP 412会否使>>> sys.getsizeof([Slots() for i in range(1000)])
Out[1]: 4516
>>> sys.getsizeof([NoSlots() for i in range(1000)])
Out[1]: 4516
变得多余。
首先,阿明·里戈(Armin Rigo)正确的是您没有正确测量它。您需要测量的是对象的大小,加上值,加上__slots__
本身(仅用于__dict__
)和键(仅用于NoSlots
)。
或者您可以按照他的建议去做:
NoSlots
[当我在OS X的64位CPython 3.4上运行此命令时,我得到cls = Slots if len(sys.argv) > 1 else NoSlots
def f():
tracemalloc.start()
objs = [cls() for _ in range(100000)]
print(tracemalloc.get_traced_memory())
f()
的8824968
和Slots
的25624872
。因此,看起来NoSlots
实例占用88个字节,而NoSlots
实例占用256个字节。
这怎么可能?
因为Slots
与键分割__slots__
之间仍然存在两个区别。
首先,字典使用的哈希表保持在不足2 / 3rd的水平,并且它们呈指数增长并且具有最小大小,因此您将有一些额外的空间。通过查看注释良好的__dict__
,不难算出多少空间:您将拥有8个哈希存储桶,而不是5个插槽指针。
第二,字典本身不是免费的;它具有一个标准的对象标头,一个计数和两个指针。听起来可能不多,但是当您谈论的是只有几个属性的对象(请注意,most对象仅具有几个属性...)时,dict标头可以与哈希表。
当然,在您的示例中是值,因此这里涉及的唯一成本是对象本身,再加上5个插槽或8个哈希桶和dict标头,因此差异非常大。在现实生活中,source很少有多大的益处。
最后,请注意,PEP 412仅主张:
基准测试表明,面向对象程序的内存使用量减少了10%到20%考虑使用
__slots__
的位置。要么节省下来的钱如此之多,以至于不使用__slots__
都是荒谬的,或者您真的需要挤出最后15%的费用。或者您希望通过谁也不知道的什么,子类可能需要储蓄作为子类,你建立一个ABC或其他类。无论如何,在没有__slots__
的情况下,您可以获得一半的收益,甚至三分之二的收益,仍然很少会满足;您仍然需要使用__slots__
。
[真正的胜利是在不值得使用__slots__
的情况下;您将免费获得一点好处。
((当然,肯定有一些程序员过度使用__slots__
,如果您幸运的话,这种改变可以说服他们中的一些人将精力投入到微优化其他不重要的事情上。)
__slots__
,它很少返回您期望的结果。例如,在这种情况下,它计算对象的“大小”而不考虑其sys.getsizeof()
的大小。我建议您通过测量创建100'000个实例的实际内存使用量来重试。