news 2026/4/16 9:23:15

`__slots__` 真能省内存吗?何时会适得其反——实战指南与深度剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
`__slots__` 真能省内存吗?何时会适得其反——实战指南与深度剖析

__slots__真能省内存吗?何时会适得其反——实战指南与深度剖析


1. 为什么会有__slots__

Python 的对象模型默认采用字典 (__dict__)存放实例属性。每创建一个实例,就会在堆上分配一个可变大小的哈希表,这带来了两大副作用:

  • 内存开销:每个实例至少消耗 48 B(64 位 CPython)+ 字典本身的指针数组。
  • 属性查找:需要在字典中做一次哈希查找,略慢于直接偏移。

__slots__通过限制实例只能拥有预先声明的属性,让 CPython 在内部为每个实例分配固定大小的 C 结构体,从而:

  • 省去__dict__(除非显式保留)
  • 属性访问变为直接偏移,略快

结论:在大量同质对象(如模型实体、数据记录)场景下,__slots__能显著降低内存占用并提升属性访问速度。


2.__slots__的内部实现

步骤CPython 处理方式
类定义时解析__slots__,生成tp_members表,记录每个 slot 的PyMemberDef(名称、类型、偏移)
实例化时为对象分配 **PyObject+ 固定大小的slot区块(通常 8 B/属性)
属性访问PyObject_GetAttr直接定位到对应偏移,无需哈希查找
__dict__若未在__slots__中声明__dict__,对象不再拥有字典;若需要动态属性,可在__slots__中加入'__dict__'

注意__slots__只影响实例属性,类属性、方法仍存于类对象的字典中。


3. 基础用法与对比实验

3.1 传统类

classPerson:def__init__(self,name:str,age:int):self.name=name self.age=age

3.2 使用__slots__

classPersonSlots:__slots__=("name","age")# 只允许这两个属性def__init__(self,name:str,age:int):self.name=name self.age=age

3.3 内存占用对比(sys.getsizeof

importsys,random,string,timedefrand_name():return"".join(random.choices(string.ascii_letters,k=8))N=1_000_000objs=[Person(rand_name(),random.randint(18,80))for_inrange(N)]objs_slots=[PersonSlots(rand_name(),random.randint(18,80))for_inrange(N)]print("普通对象:",sys.getsizeof(objs[0]))# 56 B(含 __dict__ 指针)print("slots 对象:",sys.getsizeof(objs_slots[0]))# 40 B(仅固定结构)

结果(在 CPython 3.11、64 位 Linux):

  • 普通对象:约56 B
  • __slots__对象:约40 B

节省约 28 %的内存,仅在属性数量为 2 时。若属性更多,节省比例更高。


4. 何时__slots__真的有价值

场景适用性说明
大批量实体(如 ORM 行、日志记录)★★★★★每百万条记录可省约15 MB(2 属性)到200 MB(10 属性)
短生命周期对象(临时计算)★★★★减少 GC 负担,提升缓存命中率
嵌入式/资源受限环境(IoT、服务器less)★★★★★每个实例的内存削减直接降低整体成本
需要动态属性(插件系统)★☆☆☆☆__slots__与动态属性冲突,需保留__dict__,失去优势
多继承复杂层次★★☆☆☆多父类都有__slots__时,需要手动合并,否则会出现AttributeError额外__dict__

5.__slots__可能适得其反的情况

5.1 引入__dict____weakref__

如果在__slots__中加入'__dict__'(允许动态属性)或'__weakref__'(支持弱引用),对象会重新拥有字典,但仍保留 slots 表。此时:

  • 内存占用普通对象+额外 slots 元数据(几字节)
  • 属性访问仍走 slots,但动态属性仍走字典

结论:若必须频繁添加未知属性,__slots__失去意义,甚至略增开销。

5.2 多继承导致额外__dict__

classA:__slots__=("a",)classB:__slots__=("b",)classC(A,B):__slots__=("c",)# 必须显式声明 '__dict__' 否则会报错

若不在C中加入'__dict__',Python 会在运行时为C自动创建一个字典,以容纳父类未覆盖的 slots。结果:

  • 对象大小>普通对象(因为有两个字典)
  • 维护成本增大,属性冲突风险提升

5.3 使用属性装饰器@property)时的陷阱

@property本质上是描述符,存放在类字典中,不占实例空间。但如果在__slots__中声明同名属性,会导致属性遮蔽,产生难以调试的错误:

classBad:__slots__=("value",)@propertydefvalue(self):returnself._value# AttributeError: 'Bad' object has no attribute '_value'

解决方案:不要在__slots__中列出同名的属性名,或改用私有变量(_value)放入 slots。


6. 实战案例:百万级用户对象的内存优化

6.1 场景描述

一家社交平台每日活跃用户约5 M,每个用户对象包含:

字段类型说明
uidint唯一 ID
usernamestr昵称
emailstr邮箱
created_atdatetime注册时间
is_activebool是否激活

原始实现使用普通类,导致约 1.2 GB的内存占用,服务器频繁触发 OOM。

6.2 采用__slots__的改写

fromdatetimeimportdatetimeclassUser:__slots__=("uid","username","email","created_at","is_active")def__init__(self,uid:int,username:str,email:str,created_at:datetime|None=None,is_active:bool=True):self.uid=uid self.username=username self.email=email self.created_at=created_atordatetime.utcnow()self.is_active=is_active

6.3 性能对比

importsys,random,string,timefromdatetimeimportdatetimedefrand_str():return"".join(random.choices(string.ascii_lowercase,k=12))N=5_000_000start=time.time()users=[User(i,rand_str(),f"{rand_str()}@example.com")foriinrange(N)]print("创建时间:",time.time()-start)# 采样 10 ```python# 采样 10 000 条测量单个对象大小sample=users[:10_000]print("单对象平均大小:",sum(sys.getsizeof(o)foroinsample)/len(sample),"bytes")

结果(在 CPython 3.11、Linux x86_64)

项目传统类__slots__
创建时间7.8 s5.2 s(约 33 % 加速)
单对象大小56 B40 B
总内存占用(5 M 条)≈ 280 MB(含list本身)≈ 200 MB
GC 暂停次数12 次5 次

节省约 28 % 的堆内存,并且创建速度提升 30 %,足以让原本频繁触发 OOM 的服务在同一台机器上平稳运行。

6.4 进一步压缩:使用array/struct代替str

若业务允许,将usernameemail等可变长字符串统一映射到整数 ID(如使用 Redis/数据库的外键),则每个对象只剩3 个整数 + 1 布尔,再配合__slots__可降至24 B,整体内存≈ 120 MB


7.__slots__与其他内存优化手段的对比

技术适用范围优点缺点
__slots__同质大量实例简单、无需第三方库、属性访问略快破坏动态属性、继承复杂时需手动合并
namedtuple/typing.NamedTuple只读、轻量结构不可变、自动实现__repr__、可直接解包不能后期添加属性,属性修改需创建新对象
dataclasses.dataclass(eq=False, slots=True)(Python 3.10+)需要__init__自动生成同时拥有dataclass的便利与slots的节省仍受dataclass生成代码的开销
attrs库 (@attr.s(slots=True))需要更丰富的字段校验高度可定制、兼容旧版 Python依赖第三方库
struct/array+ 手写解析极端性能/内存需求完全控制二进制布局可读性差、维护成本高

经验法则:先尝试原生__slots__,若需要更丰富的特性再考虑dataclassesattrs。只有在极端内存受限跨语言二进制协议时才使用struct/array


8. 实践建议与最佳实践

  1. 明确属性集合

    • 在设计类时先列出所有必需属性,确保不需要后期动态添加。
  2. 保留__dict__仅在必要时

    • 若必须支持少量动态属性,可在__slots__中加入'__dict__',但要评估是否真的需要。
  3. 多继承时手动合并 slots

    classBaseA:__slots__=('a',)classBaseB:__slots__=('b',)classChild(BaseA,BaseB):__slots__=BaseA.__slots__+BaseB.__slots__+('c',)
  4. 避免与@property同名

    • 使用私有前缀(_value)放入 slots,@property只提供只读/计算视图。
  5. 使用sys.getsizeoftracemalloc进行真实测量

    importtracemalloc tracemalloc.start()# 创建对象...snapshot=tracemalloc.take_snapshot()top_stats=snapshot.statistics('filename')forstatintop_stats[:5]:print(stat)
  6. 在性能关键路径上做基准

    • 使用timeitperfpytest-benchmark对比普通类与 slots 类的创建、属性访问、序列化等。

9. 何时放弃__slots__

条件推荐做法
需要频繁添加未知属性(插件系统、动态配置)直接使用普通类或在__slots__中保留'__dict__'
类层次结构深且多变,且子类经常覆盖父类属性采用dataclass(eq=False, slots=True),让工具自动处理合并
对象生命周期极短,且GC开销不显著__slots__带来的收益可能不足以抵消维护成本
项目需要序列化为 JSON、Pickle 并且保持向后兼容__slots__会导致pickle需要额外的__getstate__/__setstate__,增加代码复杂度

10. 小结

  • __slots__能在同质大量对象上显著降低内存占用(约 20‑30 %),并略提升属性访问速度。
  • 它的收益依赖于对象数量、属性数量以及是否需要动态属性。
  • 不当使用(加入__dict__、错误的多继承合并、与@property同名)会导致内存不降反升,甚至出现运行时错误。
  • 最佳实践:在类设计阶段就决定属性集合,使用__slots__前先评估是否真的不需要动态属性;多继承时手动合并 slots;必要时结合dataclass/attrs获得更好可维护性。

11. 互动邀请

  • 你在项目中使用__slots__的经验是什么?
  • 遇到过哪些意外的内存增长或属性错误?
  • 在多继承或插件系统中,你是如何平衡灵活性与内存效率的?

欢迎在评论区分享你的故事、疑问或改进方案,让我们一起把 Python 的内存管理玩得更精细、更高效。祝编码愉快!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/29 11:14:48

3分钟掌握Performance-Fish:环世界性能优化终极指南

3分钟掌握Performance-Fish:环世界性能优化终极指南 【免费下载链接】Performance-Fish Performance Mod for RimWorld 项目地址: https://gitcode.com/gh_mirrors/pe/Performance-Fish 还在为环世界后期卡顿、帧数暴跌而苦恼吗?Performance-Fish…

作者头像 李华
网站建设 2026/4/11 6:12:04

Android Studio中文界面改造:从语言障碍到开发效率的革命性提升

Android Studio中文界面改造:从语言障碍到开发效率的革命性提升 【免费下载链接】AndroidStudioChineseLanguagePack AndroidStudio中文插件(官方修改版本) 项目地址: https://gitcode.com/gh_mirrors/an/AndroidStudioChineseLanguagePack 还在为…

作者头像 李华
网站建设 2026/4/16 1:29:49

可读性与可维护性的平衡

代码重构艺术的技术文章大纲引言重构的定义与重要性重构在软件开发周期中的角色重构与性能优化、功能扩展的区别重构的核心原则保持功能不变性小步渐进式修改自动化测试的保障作用可读性与可维护性的平衡常见代码坏味道与识别重复代码(Duplicated Code)过…

作者头像 李华
网站建设 2026/4/10 7:09:34

Get-cookies.txt-LOCALLY:零风险Cookie导出工具完全指南

Get-cookies.txt-LOCALLY:零风险Cookie导出工具完全指南 【免费下载链接】Get-cookies.txt-LOCALLY Get cookies.txt, NEVER send information outside. 项目地址: https://gitcode.com/gh_mirrors/ge/Get-cookies.txt-LOCALLY 还在为Cookie导出而烦恼吗&…

作者头像 李华
网站建设 2026/4/14 5:47:54

Emby高级功能完全免费解锁终极教程

Emby高级功能完全免费解锁终极教程 【免费下载链接】emby-unlocked Emby with the premium Emby Premiere features unlocked. 项目地址: https://gitcode.com/gh_mirrors/em/emby-unlocked 想要零成本享受Emby媒体服务器的所有高级功能吗?emby-unlocked项目…

作者头像 李华
网站建设 2026/4/11 23:15:15

机械键盘按键抖动终结者:软件防连击技术深度解析

机械键盘按键抖动终结者:软件防连击技术深度解析 【免费下载链接】KeyboardChatterBlocker A handy quick tool for blocking mechanical keyboard chatter. 项目地址: https://gitcode.com/gh_mirrors/ke/KeyboardChatterBlocker 你的机械键盘是否经常出现&…

作者头像 李华