Python 对象模型与内存
一切皆对象:对象有身份类型值;CPython 以引用计数为主回收,辅以循环 GC。
#concept
#growing
#dev
#python
#python
[!info] related notes
Python 对象模型与内存
这个概念为什么出现
id、共享、循环引用、__slots__ 内存——都依赖“对象头 + 引用”这一层。本篇给 CPython 直觉,并标明实现边界。
[!abstract] 一句话理解 名字绑定对象,对象有身份、类型和值;CPython 使用引用计数并辅以循环 GC。具体回收时机和内存布局不是 Python 语言保证。
最小可观察示例
场景:观察对象身份与引用变化
# object_identity_demo.py
# 业务意图:展示同一 list 多引用与 is。
# 教学点:对象身份;引用共享。
def main() -> None:
a = ["order"]
b = a
print("id equal?", id(a) == id(b), "is?", a is b)
b.append("line")
print(a)
if __name__ == "__main__":
main()
建议运行:
python object_identity_demo.py
期望输出:
id equal? True is? True
['order', 'line']
结合场景再看三个关注点
- 身份稳定于对象生命周期。
- 共享写可见。
- 引用计数细节用工具观察,不靠猜。
核心概念与准确模型
- 每个对象:身份、类型、值
- CPython:
PyObject头含引用计数等 - 分配器随构建而异(例如 pymalloc、free-threaded 构建中的 mimalloc);对象回收不等于内存立即归还操作系统。
- slots 可避免为实例自动创建 dict;若基类已有 dict,或显式声明 dict,实例仍可有字典。
边界情况与反直觉行为
- intern 使某些字面量身份重合
- 循环引用 单靠计数不够
- free-threaded 引用计数可能延迟释放;immortal 对象也不按普通对象的引用计数回收。不要靠 del 保证文件或锁及时释放,应使用上下文管理器。
常见误区
[!warning] 常见误区:id 当序列号持久化 错误理解:id 全局永久唯一可存库。
正确模型:只在进程内生命周期有意义。
工程实践
- 大对象及时释放引用
- tracemalloc 观察被跟踪的 Python 分配;不能据此推断所有原生库分配或进程 RSS。
- 热路径对象池要非常谨慎
本节总结
对象模型连接语言使用与运行时成本。下一篇专讲 GC 与循环。
自测题
- 名字删除是否立即释放对象?
- 为什么 id 不适合持久化?
参考答案
- del 只删除绑定;其他引用仍可保留对象。常规 CPython 引用计数归零通常触发回收,但不能将此作为跨实现、跨构建的资源释放契约。
- 可复用且仅进程内有效。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Data model | 规范 | 对象 |
| gc module | 文档 | 回收接口 |
| Free-threaded Python | 文档 | 回收延迟与分配器差异 |