Agent 消息对象用了 slots 之后,发现子类继承它时内存优化失效,怎么排查和修复?
🔍 出现这个现象,十有八九是子类默默地生成了 dict,把父类辛苦省下的内存又吃回去了。
🧬 根本原因:继承链上丢了 slots¶
看个典型错误:
class BaseMessage:
__slots__ = ('role', 'content')
class ChatMessage(BaseMessage):
def __init__(self, role, content, timestamp):
self.role = role
self.content = content
self.timestamp = timestamp # ❌ 这里会报错,因为没有槽位
发现报错,于是有人这样“修”:
class ChatMessage(BaseMessage):
pass # 没定义 __slots__
msg = ChatMessage()
msg.role = "user"
msg.content = "hello"
msg.timestamp = 123 # 竟然能跑!但 __dict__ 已经悄悄回来了
当子类没有定义 slots,Python 会自动为它创建 dict,即使父类有 slots 也白搭。父类的槽位仍然有效,但子类多出来的属性全跑进 dict,优化瞬间失效。
🩺 如何排查¶
# 1. 直接看实例有没有 __dict__
msg = ChatMessage()
print(hasattr(msg, '__dict__')) # True → 内存泄漏实锤
# 2. 对比内存占用
import sys
base = BaseMessage()
base.role = "u"; base.content = "c"
print(sys.getsizeof(base)) # 比如 56
child = ChatMessage()
child.role = "u"; child.content = "c"; child.timestamp = 123
print(sys.getsizeof(child)) # 可能会大很多,且包含字典开销
# 3. 检查类的 __slots__ 是否被继承
print(ChatMessage.__slots__) # 如果只打印父类的,没包含新属性,甚至没 __slots__ 属性
如果看到 dict 存在,且 slots 里没有声明新增属性,问题就定位到了。
🛠️ 修复方案¶
✅ 正确做法:子类显式定义 slots,只写新增属性¶
class ChatMessage(BaseMessage):
__slots__ = ('timestamp',) # 只写新增的,不要重复 'role', 'content'
def __init__(self, role, content, timestamp):
self.role = role
self.content = content
self.timestamp = timestamp
这样内存布局就会变成:父类的槽 + 子类的槽,一个字典都不会有。
🔁 如果你用 dataclass(slots=True),继承也要小心¶
from dataclasses import dataclass
@dataclass(slots=True)
class BaseMessage:
role: str
content: str
@dataclass(slots=True) # 子类也一定要加 slots=True
class ChatMessage(BaseMessage):
timestamp: int = 0
如果子类忘了 slots=True,问题完全一样。
📌 特殊情况:需要 weakref¶
如果项目中用了弱引用(比如大量消息对象被缓存追踪),记得在 slots 里加上 'weakref',否则弱引用会失效或报错。
子类不用重复加,weakref 会自动沿继承链生效。
🧪 验证修复效果¶
msg = ChatMessage("user", "hello", 1234567890)
print(hasattr(msg, '__dict__')) # False → 优化生效
print(sys.getsizeof(msg)) # 与预期槽位大小一致,无字典膨胀
一句话总结修复规则:只要继承链上任何一个类没定义 slots,就会在那一刻产生 dict,内存优化就断了。 所以每个子类都要显式写出自己的 slots,哪怕它是空的(slots = ()),用来“封印”字典的生成。