Python 对象的内存布局与引用计数解析
技术百科
舞夢輝影
发布时间:2026-01-26
浏览: 次 Python对象头含ob_refcnt和ob_type字段,普通对象头16字节,可变长对象额外有ob_size;引用计数增减取决于是否获得新引用,del仅减计数不保证立即释放,小整数和字符串缓存会干扰观察。
Python 对象头里藏着什么
每个 Python 对象在内存中都以 PyObject 或其子结构(如 PyVarObject)开头,这是 CPython 实现的底层约定。对象头固定包含两个字段:ob_refcnt(引用计数)和 ob_type(指向类型对象的指针)。对普通对象(比如 int、str),头大小是 16 字节(64 位系统下);可变长对象(如 list)额外带一个 ob_size 字段记录元素个数。
你可以用 sys.getsizeof() 粗略看对象总开销,但它不包括所引用对象的内存(比如 list 中元素本身);真正布局细节得查 CPython 源码里的 Include/object.h。
常见误区:以为 id() 返回的是“地址偏移”,其实它返回的是对象首字节地址(在 CPython 中等价于 &PyObject),但这个值不能直接做指针运算——你没拿到真正的 C 指针,且解释器可能启用地址空间布局随机化(ASLR)。
引用计数怎么增减,哪些操作会漏掉
CPython 用引用计数做主要内存管理,但不是所有赋值或传参都会让计数+1。关键看是否“拥有新引用”:
-
x = y:仅当y是新绑定到局部/全局变量时,y的引用计数才+1;如果是函数参数传入,调用时已由解释器在栈帧中完成计数更新 -
append()、extend()、__setitem__等容器操作:会为存入的对象增加引用计数 -
del x或变量离开作用域:减少引用计数;若降为 0,立即触发tp_dealloc -
gc.collect()不影响引用计数逻辑——它只处理循环引用,而循环引用中的对象引用计数永远 >0
容易踩的坑:ctypes 或 C 扩展中手动调用 Py_INCREF/Py_DECREF 忘记配对,会导致悬垂指针或提前释放;还有闭包捕获变量时,即使外层函数返回,内层函数仍持有所需对象的引用,计数不会降为 0。
为什么 del 不一定立刻释放内存
del x 只是解除名字绑定并减少引用计数,是否真正释放取决于该对象是否还有其它活跃引用。例如:
a = [1, 2, 3] b = a del a # 此时 b 仍持有引用,列表对象未释放 print(b) # [1, 2, 3],正常输出
更隐蔽的情况是隐藏引用:
- 异常帧中保留了局部变量(
sys.exc_info()活跃时) - 装饰器、lambda、生成器表达式中形成的闭包引用
-
weakref不影响计数,但强引用哪怕只在一个dict的键里(如{obj: 42}),也会阻止释放
验证方式:打印 sys.getrefcount(obj)(注意这个函数调用本身会让计数临时+1,结果要减 1 才准)。
小整数与字符串的缓存机制干扰观察
为了性能,CPython 对小整数(默认 -5 到 256)和短字符串做了驻留(interning),导致看似不同的变量实际指向同一对象:
x = 100 y = 100 print(x is y) # True —— 引用同一对象,引用计数至少为 2
这种共享会让引用计数行为不符合直觉。绕过方法:
- 用
int("100")或eval("100")构造非驻留整数(但不推荐用于生产) - 字符串用拼接打破驻留:
s1 = "hello" + ""和s2 = "hello"可能不同(取决于编译期优化)
- 调试时优先用自定义类实例,避免内置类型缓存干扰
真正想看清引用变化,最好从一个干净的模块开始,禁用交互式环境缓存,并用 gc.disable() 排除分代回收干扰。
# 的是
# 这是
# 也会
# 所需
# python
# 会让
# 绑定
# 自定义
# 可以用
# app
# 循环
# 对象
# int
# 字节
# 指针
# 字符串
# 为什么
# 栈
# 作用域
# 闭包
# Object
# Lambda
# 局部变量
# 全局变量
# include
# append
# 变长
# 降为
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- php查询数据怎么导出csv_查询结果转csv文件
- 如何使用Golang指针与结构体结合_修改结构体内
- Windows Defender扫描失败怎么办_安
- Win11怎么关闭开机声音_Win11系统启动提示
- 如何用::实现工具类方法调用_php静态工具类设计
- Win11如何关闭游戏模式 Win11禁用Xbox
- Win11怎么设置快速访问_Windows11文件
- Win11怎么查看显卡温度 Win11任务管理器查
- 如何使用Golang实现微服务状态监控_Golan
- Windows家庭版如何开启组策略(gpedit.
- mac怎么右键_MAC鼠标右键设置与触控板手势技巧
- php485函数怎么捕获异常_php485错误处理
- Win11蓝牙开关不见了怎么办_Win11蓝牙驱动
- Win10怎样清理C盘浏览器缓存_Win10清理浏
- c++怎么使用std::filesystem遍历文
- Python数据挖掘进阶教程_分类回归与聚类案例解
- c++的STL算法库find怎么用 在容器中查找指
- 如何在Golang中优化文件读写性能_使用缓冲和并
- Win10如何更改开机密码_Windows10登录
- PHP主流架构怎么集成Redis缓存_配置步骤【方
- php订单日志怎么导出excel_php导出订单日
- Python与GPU加速技术_CUDA与Numba
- Win11关机快捷键是什么_Win11快速关机方法
- php和redis连接超时怎么办_phpredis
- Win11摄像头无法使用怎么办_Win11相机隐私
- Windows蓝屏错误0x0000002C怎么解决
- 如何在 Go 中调用动态链接库(.so)中的函数
- MAC如何隐藏文件夹及文件_MAC终端命令隐藏与第
- Win11怎么关闭定位服务 Win11禁止应用获取
- Windows10如何更改计算机工作组_Win10
- Win11怎么设置任务栏对齐方式_Windows1
- 如何在Golang中定义接口_抽象方法和多态实现
- php8.4如何调用com组件_php8.4win
- Win11怎么关闭防火墙通知_屏蔽Win11安全中
- Windows10无法识别USB设备描述符请求失败
- c++协程和线程的区别 c++异步编程模型对比【核
- Win11怎么设置组合键快捷方式_Windows1
- Windows10怎样设置家长控制_Windows
- 网站内页做seo排名怎么做?
- 如何使用Golang安装依赖库_管理模块和第三方包
- Python列表推导式与字典推导式教程_简化代码高
- 如何在包含多值的列中精准搜索指定演员?
- Win11怎么看电池循环次数_Win11笔记本电池
- c++ stringstream用法详解_c++字
- Win10怎么卸载迅雷_Win10彻底卸载迅雷方法
- Win10怎样卸载TeamViewer_Win10
- c++怎么编写动态链接库dll_c++ __dec
- Win10如何备份注册表_Win10注册表备份步骤
- mac怎么安装adb_MAC配置Android A
- 如何在Golang中处理JSON字段缺失_Gola


QQ客服