__slots__为什么能节省内存?

注:本文是对http://www.datadependence.com/2016/07/pythonic-code-video-series-slots/ 的翻译。说是翻译,但不会逐字逐句翻译。宗旨是回答标题的问题,所以也会增加自己的理解。
很多人都看过Oyster.com的这篇相当当的文章:
SAVING 9 GB OF RAM WITH PYTHON’S _SLOTS_
为了能够理解_slots_,最好的方式是先了解域是如何关联python类的。考虑下面这个类:

 class Measurement:
     def __init__(self,x,y,value):
         self.x = x
         self.y = y
         self.val = value

假如我们创建少量几个这个类的实体,通过初始化方法设置值,并给一个实体动态增加属性及值,如下:

m1 = Measurement(1,2,"Happy")
m2 = Measurement(7,10,"Crazy")

那在内存中的存储大概类似如下:
这里写图片描述

从上图可以看到,每个实体都有指向一个包括属性名及属性值的_dict_。我们可以打印出来:

print(m1.__dict__) #{'x':1,'y':2,'val':'happy'}
print(m2.__dict__) #{'x':7,'y':10,'other':True,'val':'Crazy'}

如果我们不是像m2.other = True那样在类创建好后定制属性。并且,我们要创建非常非常多的实例(比如,百万级),那这种方式就是非常低效的——我们需要在内存中存储对应实体数量包含重复键的字典。

使用_slots_

只要简单的修改下Measurement类的属性的存储就能消除重复,得到一个1比1的字典分配。

class Measurement:
    __slots__ = ['x','y','val']
    def __init__(self,x,y,value):
        self.x = x
        self.y = y
        self.val = value

(译者注:原文是在python3上实现的。因为python3只有新式类,无需任何指定。但是如果是在python2(译者熟悉的2.6/2.7)上。上面的写法就是错误的,具体看下代码,运行后就知道了)

class Measurement(object):
    __slots__ = ['x','y','val']
    def __init__(self,x,y,value):
        self.x = x
        self.y = y
        self.val = value

class Measurement_old:
    __slots__ = ['x','y','val']
    def __init__(self,x,y,value):
        self.x = x
        self.y = y
        self.val = value

m1 = Measurement(1,2,"new")
m2 = Measurement_old(2,3,"old")
print dir(Measurement)
print dir(Measurement_old)
print type(Measurement.x) #<type 'member_descriptor'>
#print m1.__dict__ #AttributeError
print m2.__dict__ #{'y': 3, 'x': 2, 'val': 'old'}

这样,内存中的存储就大概类似这样的了:
这里写图片描述

现在从图可以看出,属性名和Measurement类关联,而不再是和它的实体关联。好处是显而易见的。

<think>嗯,用户问的是为什么Python中的__slots__比字典(dict)更省内存。我得先回忆一下相关的知识,然后组织一个清晰的解释。 首先,Python中每个对象默认都有一个__dict__属性,用来存储实例的属性和方法。这个字典允许动态地添加属性,但也会占用额外的内存。比如,每个实例的字典需要维护键值对,这会带来一定的内存开销,尤其是当创建大量实例时。 而__slots__是一个类变量,用来显式声明类可以拥有的属性。当类定义了__slots__后,实例就不会再创建__dict__,而是使用固定的数组来存储属性。这样,每个实例就不需要维护一个完整的字典结构,从而节省内存。 接下来需要具体解释内存节省的原因。字典作为动态数据结构,本身需要存储哈希表、键和值等信息,每个字典实例都会有额外的内存开销。比如,在64位Python中,一个空的字典可能占用几百字节的内存。而使用__slots__后,实例的属性存储在一个固定大小的数组中,每个属性对应一个位置,减少了额外的结构开销。 可能还需要提到内存对齐的问题。__slots__分配的数组是紧凑排列的,没有额外的空间浪费,而字典由于哈希表的实现,可能会有空闲的桶(bucket),导致内存使用不够高效。 然后,应该举一个例子,比较使用__dict__和__slots__的类的内存占用差异。比如,定义一个普通类和一个使用__slots__的类,创建大量实例后用sys.getsizeof或者pympler工具测量内存使用量,显示后者更节省。 另外,需要注意__slots__的局限性,比如不能动态添加新的属性,所有属性必须在__slots__中声明,这可能会影响灵活性。同时,继承带有__slots__的类时,子类也需要定义自己的__slots__,否则可能失去内存优化的效果。 可能用户还会想知道具体的节省幅度,可以提到在某些情况下,内存节省可能达到40%-50%,尤其是在实例数量庞大时效果显著。例如,处理数百万个对象时,使用__slots__可以显著减少内存占用,提升程序性能。 最后,总结一下__slots__适用的场景:当需要创建大量实例,且属性固定不变时,使用__slots__可以有效节省内存。但也要权衡灵活性的损失,确保不需要动态添加属性。</think>Python 中 `__slots__` 比普通类的字典 (`__dict__`) 更省内存,主要原因在于底层存储结构的差异。以下分步解释: --- ### 1. 普通类的内存开销 - **默认行为**:Python 类实例的属性会存储在动态字典 `__dict__` 中。 - **字典特性**: - 每个实例的 `__dict__` 是一个哈希表,需要存储键值对、哈希值、引用计数等信息。 - 字典本身会预留额外空间以支持快速插入(内存预分配)。 - **内存开销**: - 在 64 位 Python 中,一个空字典占用约 **240 字节**。 - 每个属性需要额外存储键和值的引用(各 8 字节)。 --- ### 2. 使用 `__slots__` 的优化 - **显式声明属性**:`__slots__` 会为实例分配固定大小的数组来存储属性,而不是动态字典。 - **内存结构**: - 直接通过偏移量访问属性(类似 C 结构体)。 - 不需要存储键名、哈希表等元数据。 - **内存开销**: - 数组每个槽位只需存储值的引用(8 字节)。 - 无预分配空间,内存紧凑无浪费。 --- ### 3. 对比示例 假设类有 3 个属性: ```python # 普通类 class Normal: def __init__(self, a, b, c): self.a = a self.b = b self.c = c # 使用 __slots__ class Slot: __slots__ = ('a', 'b', 'c') def __init__(self, a, b, c): self.a = a self.b = b self.c = c ``` - **内存占用**(通过 `sys.getsizeof` 测量): - `Normal` 实例:约 **240 字节(字典) + 3*8 字节(属性) ≈ 264 字节` - `Slot` 实例:仅 **3*8 字节 = 24 字节`(无字典开销) --- ### 4. 其他优化点 - **减少间接访问**:直接通过偏移量访问属性,速度更快。 - **内存对齐**:数组存储更紧凑,减少缓存未命中。 --- ### 5. 限制与权衡 - **无法动态添加属性**:`__slots__` 会禁用 `__dict__`,灵活性下降。 - **继承问题**:如果父类有 `__slots__`,子类需显式定义自己的 `__slots__`。 --- ### 总结 `__slots__` 通过以下方式省内存: 1. 消除 `__dict__` 的哈希表结构开销 2. 用紧凑数组代替松散字典存储 3. 避免内存预分配浪费 适用于需要创建海量实例且属性固定的场景(如科学计算、数据处理),但对灵活性有要求时需谨慎使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值