我们的程序创建了大量的实例,为此占用了大量的内存。对于那些主要用作简单数据结构的类,通常可以在类定义中增加__slots__属性,以此来大量减少对内存的使用。如下:
In [16]: class Date:
...: __slots__ = ['years', 'month', 'day']
...: def __init__(self, year, month, day):
...: self.year = year
...: self.month = month
...: self.day = day
当定义了__slots__属性时,Python就会针对实例采用一种更加紧凑的内部表示。不再让每个实例都创建一个__dict__字典,现在的实例时围绕着一个固定长度的小型数组来构建的,这和一个元祖或者列表很相似。在__slots__中列出的属性名会在内部映射到这个数组的特定索引上。使用__slots__带来的副作用是我们没法再对实例添加任何新的属性了,也就是说我们被限制为只允许使用__slots__中列出的那些属性名。
使用__slots__节省下来的内存根据创建的实例数量以及保存的属性类型而有所不同。但是,一般来说使用的内存量相当于将数据保存在元祖中。为了有一个直观的感受,我们举个例子:在64位版本的Python中,不使用__slots__保存一个单独的Date实例,则需要428个字节的内存。如果定义了__slots__,内存用量将下降到156字节。在一个需要同时处理大量Date实例的程序中,这将显著减少总的内存用量。
尽管__slots__看起来似乎是一个非常有用的特性,但是在大部分代码中都应该尽量别使用它。Python中有许多部分都依赖于传统的基于字典的实现。此外,定义了__slots__属性的类不支持某些特定的功能,比如多重继承。就大部分情况而言,我们应该只针对那些在程序中被当做数据结构而频繁使用的类上采用__slots__技法(如果你的程序创建了上百万个特定的类实例)。
关于__slots__有一个常见误解,那就是这是一种封装工具,可以阻止用户为实例添加新的属性。尽管这的确是使用__slots__所带来的副作用,但这绝对不是使用__slots__的原本意图。相反,人们一直以来都把__slots__当做一种优化工具。