用 __slots__ 可将实例内存压缩至原来的1/3~1/5,因禁用__dict__和__weakref__,属性转为固定偏移存储;需显式声明属性名,父类启用则子类也须定义,否则优化失效。

用 __slots__ 禁用动态属性能省多少内存?
默认情况下,Python 每个实例都带一个 __dict__ 字典来存属性,哪怕只存 2–3 个字段,也要额外占用 ~240 字节。上万对象时,这部分开销就非常可观。
加 __slots__ 后,实例不再有 __dict__ 和 __weakref__,属性直接映射到预分配的固定偏移量,内存可压缩到原来的 1/3~1/5。
- 必须显式声明所有允许的属性名:
__slots__ = ('id', 'name', 'score') - 不能动态赋值未声明的属性,否则报
AttributeError: 'X' object has no attribute 'xxx' - 父类用了
__slots__,子类也得定义,否则父类的优化会被子类的__dict__覆盖 - 如果需要偶尔支持动态属性,可保留
'__dict__'在 slots 元组里,但那就失去大部分优化效果
对象池(__new__ + 缓存)适合哪些场景?
不是所有对象都适合池化。只有满足「创建开销大 + 生命周期短 + 实例可复用」三个条件时才值得做,比如网络连接、线程本地配置、坐标点(若业务中大量重复构造相同值)。
常见误用是拿它优化普通数据容器(如 User),反而因哈希/查找引入额外开销,且容易引发状态残留 bug。
立即学习“Python免费学习笔记(深入)”;
- 用弱引用字典
weakref.WeakValueDictionary存缓存,避免对象永久驻留 - 重写
__new__时,先查池、命中则跳过super().__new__,未命中再创建并存入池 - 注意:池化后对象状态必须重置(例如在
__init__或复用前清空字段),否则上次使用残留的数据会污染下次调用 - 并发环境下需加锁(如
threading.Lock),但锁本身可能成为瓶颈,建议按 key 分片加锁
array.array 和 struct.pack 替代对象列表真能提速?
当你要存的是同构数值数据(比如十万条 (x: float, y: float, z: float)),用对象列表是典型的空间浪费:每个 Point 实例至少 48 字节 + 3 × 24 字节浮点对象引用 + 冗余类型信息。
换成 array.array('d', [...]) 或 struct.pack('ddd', x, y, z) 打包成二进制块,内存直接降到 1/5 以内,且 CPU 缓存友好,遍历速度通常快 3–10 倍。
-
array.array只支持基础类型('i','d','B'等),不支持嵌套或混合类型 - 用
struct需自己管理偏移和解包逻辑,适合固定结构+批量读写;array更适合单值追加和随机访问 - 如果后续要频繁按字段过滤(如 “找出所有 y > 10 的点”),纯 array 会逼你写 C 风格循环,此时可考虑
numpy.ndarray—— 它底层也是紧凑内存布局,还带向量化操作
为什么 __del__ 和弱回调不适合内存敏感场景?
很多人想靠 __del__ 主动归还资源或清理缓存,但这不可靠:CPython 中它依赖引用计数归零触发,PyPy/其他实现甚至不保证调用时机;循环引用下更可能永不触发。
弱引用回调(weakref.ref(obj, callback))看似优雅,但 callback 执行时机不确定,且每次注册都会产生额外对象(弱引用本身也有开销),在高频创建/销毁场景下反而增加 GC 压力。
- 真正可控的方式是显式生命周期管理:用上下文管理器(
with)、手动.close()、或把对象生命周期绑定到某个明确的 owner 上 - 如果必须自动清理,优先用
weakref.finalize(比__del__更可靠),但仍要避免在 finalize 里做耗时或依赖其他对象的操作 - 最省心的做法其实是别让对象活太久——用生成器替代一次性列表,用迭代器替代全量加载,从源头减少存活对象数
内存优化不是堆技巧,而是对数据建模方式的重新审视。一个 __slots__ 能省几百 MB,但若本该用 array 存的数据硬套 class,再怎么调参也救不回来。










