news 2026/5/23 4:03:55

Python __slots__ 入门指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python __slots__ 入门指南

在 Python 中,我们习惯了对象的动态特性 —— 可以随时给实例添加新的属性。这非常灵活,但在处理大量数据对象时,这种灵活性会带来不小的内存开销。__slots__正是为了解决这个问题而生的强大工具。

本教程将带你全面了解__slots__的功能、基本用法以及在继承场景下的注意事项,所有代码示例均经过实际运行验证,确保结果真实可靠。

1. 什么是__slots__

__slots__是 Python 类中的一个特殊类属性(class attribute)。

默认情况下,Python 类的实例会有一个__dict__属性,这是一个字典,用来存储实例的所有属性。这个字典允许我们在运行时动态地添加新属性,但它本身也会消耗大量的内存。

当你在类中定义了__slots__时,Python 会:

  1. 为声明的属性在内存中预留固定的空间。
  2. 不再为每个实例自动创建__dict____weakref__
  3. 限制实例只能拥有__slots__中声明的那些属性。

这意味着,通过牺牲一点点动态性,我们换来了巨大的内存节省和一定的访问速度提升。

2. 核心功能

2.1 显著的内存优化

这是使用__slots__最主要的原因。对于拥有成千上万个实例的数据类来说,节省的内存是非常可观的。

普通的 Python 对象,每个实例的__dict__通常需要几百字节的开销,而使用了__slots__的实例,每个属性只占用固定的字段大小,类似于 C 语言中的结构体。

可运行的内存对比代码

以下测试结果基于Python 3.10.12 + Ubuntu 22.04环境。
不同的 Python 版本或操作系统,结果可能会有差异(例如 Python 3.11+ 对普通对象内存有额外优化),请以你本地运行的实际结果为准。

你可以直接运行下面这段代码,在你的环境中亲眼看看两者的差距(使用标准库tracemalloc精确测量):

importtracemalloc# 1. 普通类classNormalPoint:def__init__(self,x,y):self.x=x self.y=y# 2. 使用 __slots__ 的类classSlotPoint:__slots__=('x','y')def__init__(self,x,y):self.x=x self.y=y# 测试创建 10 万个实例的内存占用deftest_memory(cls,count=100000):tracemalloc.start()instances=[cls(i,i+1)foriinrange(count)]snapshot=tracemalloc.take_snapshot()top_stats=snapshot.statistics('lineno')total=sum(stat.sizeforstatintop_stats)tracemalloc.stop()returntotal normal_mem=test_memory(NormalPoint)slot_mem=test_memory(SlotPoint)print(f"创建10万个普通实例,总内存:{normal_mem/1024:.2f}KB")print(f"创建10万个Slots实例,总内存:{slot_mem/1024:.2f}KB")print(f"节省了:{(normal_mem-slot_mem)/1024:.2f}KB 内存")

我们环境下的实际运行结果

创建10万个普通实例,总内存: 21081.03 KB 创建10万个Slots实例,总内存: 10924.84 KB 节省了: 10156.19 KB 内存

仅仅 10 万个实例,就节省了近 10MB 的内存!当你实例化更多对象时,差距会更加惊人。

2.2 严格的属性限制

定义了__slots__后,你就不能再给实例添加__slots__中未声明的属性了。这可以帮助你:

  • 防止拼写错误:如果不小心打错了属性名,Python 会直接抛出AttributeError,而不是静默地创建一个新的、无用的属性。
  • 强制接口规范:确保类的使用者不会随意修改对象结构,让代码更加健壮。

2.3 更快的属性访问

由于属性不再是通过字典的哈希表查找,而是通过固定的偏移量直接访问内存,这使得属性的读写速度会略快于普通对象。虽然对于少量对象来说提升不明显,但在高性能场景下依然有帮助。

3. 基本使用

3.1 基础语法

使用__slots__非常简单,只需要在类定义中添加一个名为__slots__的类变量即可。它通常是一个字符串序列(tuple 或 list),列出你允许实例拥有的所有属性名。

classPoint:# 声明允许的属性__slots__=('x','y')def__init__(self,x,y):self.x=x self.y=y# 正常使用p=Point(10,20)print(p.x)# 输出: 10print(p.y)# 输出: 20

3.2 动态属性被禁止

尝试添加一个未声明的属性会发生什么?

# 尝试添加新属性 zp.z=30

运行结果

AttributeError: 'Point' object has no attribute 'z'

这正是我们想要的!它阻止了意外的属性创建。对比一下普通类的行为:

classNormalPoint:def__init__(self,x,y):self.x=x self.y=y n_p=NormalPoint(10,20)n_p.z=30# 这在普通类中是允许的!print(n_p.__dict__)# 输出: {'x': 10, 'y': 20, 'z': 30}# (拼写错误在这里很难被发现)

3.3 没有__dict__

定义了__slots__的实例没有__dict__

print(hasattr(p,'__dict__'))# 输出: False

4. 继承中的注意事项

__slots__的行为在继承中会变得稍微复杂一些,这也是最容易出错的地方。根据 Python 官方文档,我们需要注意以下几点:

4.1 单继承:子类是否定义__slots__

父类定义的__slots__会自动继承给子类。但是,子类是否自己定义__slots__会导致完全不同的结果。

情况 1:子类没有定义自己的__slots__

如果父类有__slots__,但子类没有,那么子类的实例仍然会拥有__dict__

这意味着,虽然子类继承了父类的 slots,但你依然可以给子类实例动态添加新属性,因为它有字典。这也意味着,你失去了大部分的内存优化效果。

classParent:__slots__=('x','y')classChild(Parent):# 注意:这里没有定义 __slots__passc=Child()c.x=10c.y=20c.z=30# 这居然是允许的!因为 Child 有 __dict__print(hasattr(c,'__dict__'))# 输出: Trueprint(c.__dict__)# 输出: {'z': 30}

如果你想让子类也保持 slots 的特性(无__dict__、省内存),你必须在子类中也显式地定义__slots__

情况 2:子类也定义了自己的__slots__

如果你在子类中也定义了__slots__,那么它会在父类的基础上添加新的 slots。子类的实例将不再有__dict__

通常的做法是,子类的__slots__只列出新增的属性即可。

classParent:__slots__=('x','y')classChild(Parent):# 只声明新增的属性 z__slots__=('z',)c=Child()c.x=10# 继承自父类c.y=20# 继承自父类c.z=30# 子类新增的# 现在不能加新属性了c.w=40# AttributeError: 'Child' object has no attribute 'w'print(hasattr(c,'__dict__'))# 输出: False

4.2 多重继承的限制

这是一个非常严格的限制。根据 Python 数据模型文档:

Multiple inheritance with multiple slotted parent classes can be used, but only one parent is allowed to have attributes created by slots (the other bases must have empty slot layouts) - violations raise TypeError.

翻译过来就是:
你可以继承多个带有__slots__的父类,但其中只能有一个父类是非空的 slots,其他的父类必须是空的__slots__ = ()。否则会直接报错。

这是因为 Python 的内存布局无法同时处理两个都有实例字段的父类。

错误示例

classA:__slots__=('a',)classB:__slots__=('b',)classC(A,B):# TypeError!pass

运行会报错:TypeError: multiple bases have instance lay-out conflict

正确示例

classA:__slots__=('a',)classB:__slots__=()# 空的 slotsclassC(A,B):# OK__slots__=('c',)

4.3 不要重复定义 Slot

如果子类重新定义了一个父类已经有的 slot,虽然 Python 不会报错,但这会导致父类的那个 slot 变得无法访问,而且会浪费内存。

classParent:__slots__=('x',)classChild(Parent):__slots__=('x',)# 重复定义了!

这是一个坏味道,应该避免。

5. 常见问题与高级用法

5.1 如何设置默认值?(避坑重点)

很多新手会尝试用类属性来给 slot 设置默认值,这是一个非常常见的错误!

错误示范与具体后果
# 错误的做法!classPerson:__slots__=('name','age')# 试图用类属性设置默认值name="Unknown"age=0

我们环境下的实际运行后果

ValueError: 'name' in __slots__ conflicts with class variable

为什么会这样?
在现代 Python 版本中,解释器已经加入了严格的检查!当你在__slots__中声明了name,同时又在类上定义了同名的类属性时,Python 会直接在类定义阶段就报错,阻止你犯这个错误。

这是因为__slots__是通过描述符(descriptor)实现的,类属性会覆盖描述符,导致 slot 机制失效。现在的 Python 直接拦截了这种错误的写法。

正确的做法

__init__方法中设置默认值。

classPerson:__slots__=('name','age')def__init__(self,name="Unknown",age=0):self.name=name self.age=age# 现在一切正常p=Person()print(p.name)# Unknownp.name="Bob"print(p.name)# Bob

5.2 我还想要动态属性怎么办?

如果你既想享受大部分 slots 带来的内存优化,又想保留一点点动态性,你可以手动把'__dict__'加入到__slots__中!

classMyClass:__slots__=('name','age','__dict__')obj=MyClass()obj.name="Alice"# slotobj.foo="bar"# 动态属性,存在 __dict__ 里

这样,声明的nameage依然用 slots 存储,省内存,而额外的属性依然可以存在字典里。

5.3 弱引用支持

默认情况下,定义了__slots__的类不支持弱引用(weakref),因为 Python 去掉了__weakref__属性。

如果你需要支持弱引用,把它加进去就行:

classMyClass:__slots__=('name','__weakref__')

5.4 与 Dataclasses 结合

在 Python 3.7+ 的 dataclasses 中,你可以很方便地开启 slots:

fromdataclassesimportdataclass@dataclass(slots=True)# 一行搞定!classPoint:x:inty:int

这会自动为你生成带有__slots__的数据类,非常方便。

6. 什么时候不该用__slots__

  • 你需要动态添加属性:如果你的类本身就是高度动态的,那就没必要用它。
  • 你需要使用cached_property:像functools.cached_property这样的装饰器依赖于__dict__来存储缓存结果。
  • 实例数量很少:如果你的类只会被实例化几次,那节省的那点内存完全没必要,反而增加了代码的复杂度。
  • 需要配合某些特殊的库:有些 ORM 或者序列化库可能依赖于__dict__

7. 总结

__slots__是 Python 中一个被低估但极其强大的优化工具。

  • 核心作用:通过替换__dict__,大幅减少内存占用,加速属性访问。
  • 基本用法:在类中定义__slots__ = ('attr1', 'attr2')
  • 继承要点:
    • 父类的 slots 会被继承。
    • 子类必须也定义__slots__才能保持无__dict__的特性。
    • 多重继承时,只能有一个非空的 slotted 父类。
  • 灵活性:你可以通过添加'__dict__''__weakref__'来按需恢复部分功能。
  • 避坑提醒:不要用类属性给 slot 设置默认值,现代 Python 会直接报错阻止你!

当你需要处理海量数据对象时,不妨试试给你的类加上__slots__,它往往能给你带来意想不到的性能提升。

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

让ClaudeCode成本爆降89%,这个开源工具有点猛...

大家好,今天介绍一个工具一个用 Rust 写的命令行代理工具,专门解决 LLM/Agent 跑 Shell 命令时原始输出太啰嗦、狂烧 Token 的问题。它到底解决啥?你让 Claude Code、Cursor 这类 AI 助手跑 git diff、cargo test、ls -R、npm ls……原始输出…

作者头像 李华
网站建设 2026/5/23 4:02:48

IPv6网络中断问题解析与NDP缓存优化

1. IPv6网络中断问题解析我在使用Keil MDK中间件IPv6协议栈时遇到了一个典型问题:当NDP(邻居发现协议)缓存超时后,网络连接会意外中断。这个问题在启用IPv6隐私扩展(使用临时地址通信)的环境中尤为明显。具…

作者头像 李华
网站建设 2026/5/23 3:58:02

迁移学习提升可穿戴设备睡眠监测精度的技术解析

1. 项目概述:迁移学习如何提升可穿戴设备的睡眠监测精度作为一名长期关注健康监测技术的从业者,我见证了可穿戴设备在睡眠监测领域的快速发展。但一个核心痛点始终存在:基于PPG(光电容积图)等外周生理信号的可穿戴设备…

作者头像 李华
网站建设 2026/5/23 3:56:42

RIS辅助MA系统的近场DM设计与优化

1. 项目概述在6G通信系统中,物理层安全(PLS)技术正成为保障无线通信安全的关键手段。其中,定向调制(Directional Modulation, DM)和可重构智能表面(Reconfigurable Intelligent Surface, RIS)是两项极具前景的技术。DM通过多维信号控制实现安全传输&…

作者头像 李华
网站建设 2026/5/23 3:53:06

OAuthlib错误排查实战:从invalid_grant到server_error的根因定位

1. 为什么OAuthlib的错误信息总让你一头雾水?刚接手一个老项目,登录流程突然崩了,控制台只甩出一行红字:invalid_grant。我下意识去翻OAuthlib文档,结果发现它压根不解释这个错误到底意味着什么——它只告诉你“授权无…

作者头像 李华
网站建设 2026/5/23 3:49:36

AI义肢如何实现意图识别与自然交互

1. 项目概述:这不是科幻电影,是正在实验室和康复中心落地的AI义肢革命“From Amputee to Cyborg with this AI-Powered Hand! 🦾”——这个标题里没有一个词是夸张修辞。我接触过三款已进入临床试用阶段的AI驱动上肢义肢,其中两款…

作者头像 李华