广告
您当前的位置: 首页 >  技术 >  Python

Python 中的内存优化与 __slots__ 机制深度探秘

作者:admin 时间:2026-06-23 阅读数:10人阅读

在 Python 中,一切皆对象。当我们创建一个类的实例时,Python 默认会为该实例分配一个字典——即著名的 __dict__,用于存储该实例的属性和数据。

这种动态字典的机制赋予了 Python 极强的灵活性(你可以随时给实例动态添加新属性),但灵活性并非没有代价。字典(dict)在底层是基于哈希表实现的,为了减少哈希冲突并保持高速检索,它会预留大量的空闲槽位。当我们需要创建数十万甚至数百万个轻量级对象时,__dict__ 带来的内存开销将变得极其惊人。

为了解决这一内存痛点,Python 提供了一种优雅的类属性优化工具:__slots__

本文将带你深度剖析 Python 对象的属性存储原理,并结合实战代码展示如何利用 __slots__ 实现内存优化。


一、 默认的属性存储机制:__dict__

我们首先定义一个常规的二维坐标点类,并观察它的内部结构:

class RegularPoint:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = RegularPoint(10, 20)
print(p.__dict__)  # 输出: {'x': 10, 'y': 20}

当你执行 p.z = 30 时,Python 只是简单地将新键值对插入到了 p.__dict__ 中。 由于 __dict__ 是一个标准的哈希表,哪怕它只存了两个数字,它在内存中也占据了相当可观的空间(在 64 位系统下,一个空字典通常至少需要 100 多字节的内存,且随着数据增加而快速增长)。


二、 救星降临:什么是 __slots__

如果你非常确定你的类只需要一组固定的属性(例如坐标点永远只有 xy),那么你就可以在定义类时,通过定义 __slots__ 显式声明允许的属性名:

class SlottedPoint:
    # 显式声明允许的实例属性名
    __slots__ = ("x", "y")

    def __init__(self, x, y):
        self.x = x
        self.y = y

p = SlottedPoint(10, 20)

当你声明了 __slots__ 后,Python 的底层行为会发生重大改变:

  1. 取消 __dict__:Python 将不再为该类的实例创建 __dict__ 字典。
  2. 改用固定数组:实例属性将存放在一个结构体内部的固定大小数组中。属性的访问直接通过类级别的描述符(Descriptors)偏移量进行定位。
  3. 禁止动态添加属性:此时执行 p.z = 30 将会直接抛出 AttributeError: 'SlottedPoint' object has no attribute 'z'

三、 内存与性能实战对比

我们编写一段测试代码,直观地衡量 __slots__ 在内存和访问速度上的提升:

import sys
import time

class NormalUser:
    def __init__(self, user_id, name):
        self.user_id = user_id
        self.name = name

class SlottedUser:
    __slots__ = ("user_id", "name")
    def __init__(self, user_id, name):
        self.user_id = user_id
        self.name = name

def memory_test():
    # 创建 100,000 个实例
    n = 100000

    # 1. 普通对象内存测试
    start_time = time.time()
    normal_users = [NormalUser(i, f"user_{i}") for i in range(n)]
    normal_mem = sum(sys.getsizeof(u) + sys.getsizeof(u.__dict__) for u in normal_users)
    print(f"[普通对象] 创建 {n} 个实例耗时: {time.time() - start_time:.4f} 秒")
    print(f"[普通对象] 占用估算内存: {normal_mem / (1024 * 1024):.2f} MB")

    # 2. Slotted对象内存测试
    start_time = time.time()
    slotted_users = [SlottedUser(i, f"user_{i}") for i in range(n)]
    slotted_mem = sum(sys.getsizeof(u) for u in slotted_users)
    print(f"[Slots对象] 创建 {n} 个实例耗时: {time.time() - start_time:.4f} 秒")
    print(f"[Slots对象] 占用估算内存: {slotted_mem / (1024 * 1024):.2f} MB")

if __name__ == '__main__':
    memory_test()

运行结果分析:

在一台标准的 64 位电脑上运行此脚本,你会看到令人吃惊的差距: * 普通对象 占用估算内存可能达到 15MB - 20MB。 * Slots对象 占用估算内存通常仅有 4.5MB 左右。 * 结论__slots__ 节省了超过 70% 的内存空间!此外,由于消除了字典的哈希查找步骤,属性的读取和写入速度通常也会提升 10% - 20%


四、 __slots__ 的避坑指南

虽然 __slots__ 效果显著,但在日常开发中,有一些极其关键的规则需要遵守,否则容易产生意想不到的 Bug:

1. 继承问题

  • 如果子类继承自一个声明了 __slots__ 的父类,子类默认依然会产生 __dict__
  • 要想完全优化,子类必须也显式声明 __slots__(即使子类没有任何新属性,也要声明 __slots__ = ())。
  • 多重继承中,多个父类都声明非空 __slots__ 会导致 Python 报错(TypeError: multiple bases have instance lay-out conflict)。因此,多重继承下尽量避免使用 __slots__

2. 弱引用支持 (__weakref__)

默认情况下,声明了 __slots__ 的类无法被弱引用(weakref)。如果你的代码中需要用到弱引用(例如垃圾回收监听或特定的缓存设计),你必须在 __slots__ 中显式加入 __weakref__

class SafePoint:
    __slots__ = ("x", "y", "__weakref__")

3. 序列化与反序列化

一些第三方的 JSON 序列化或 ORM 框架在底层极其依赖对象的 __dict__ 属性。如果将这些对象强行改为 __slots__ 可能会导致框架无法正常解析或报错。因此,在配合一些依赖字典反射的库时需要谨慎使用。

总结

__slots__ 是 Python 提供给中高级开发者的“性能调优手术刀”。对于普通的业务类,为了保持代码的动态扩展性,我们依然推荐使用默认的 __dict__ 机制。但当你在编写底层基础框架、开发需要承载数百万条数据包的网络代理服务,或是像大模型智能体(Agent)开发中需要频繁在内存中维护大量轻量级上下文节点时,利用 __slots__ 锁死属性结构,能为你的应用带来难以置信的内存和性能优势。

本站所有文章、数据、图片均来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。

评论交流 (0)

正在加载评论...
头像

admin

当你还撑不起你的梦想时,就要去奋斗。如果缘分安排我们相遇,请不要让她擦肩和过。我们一起奋斗!

微信