PyEval_EvalCode对参数字典的引用计数增减规则咨询
关于PyEval_EvalCode中全局/局部字典的引用计数管理
我来帮你拆解这个问题——我之前在把Python嵌入到C项目里的时候也踩过类似的引用计数坑,官方文档确实在这个细节上语焉不详,得结合源码和实际行为来梳理。
核心认知:先明确引用计数的基本规则
在Python C API里,引用计数遵循**「谁创建/谁显式持有,谁负责释放」**的原则:
- 如果你通过
PyDict_New()、PyDict_Copy()这类函数创建了字典对象,你会获得这个对象的一个拥有引用,必须在不再使用时调用Py_DECREF()。 - 如果是通过
PyEval_GetGlobals()、PyModule_GetDict()这类API获取的字典,这是一个借用引用,你不需要负责释放,除非你显式调用了Py_INCREF()把它变成拥有引用。
PyEval_EvalCode对字典的实际引用行为
针对你提到的两种脚本场景,差异的核心在于脚本是否创建了持有全局/局部字典的对象:
1. 场景一:包含空函数的脚本
当脚本定义了函数时,Python会自动让函数的__globals__属性指向你传入的全局字典,这会给全局字典增加一个永久引用(只要函数对象存在,这个引用就会保留)。这时候:
- 你传入的全局字典的引用计数会比执行脚本前多1(来自函数对象)。
- 如果之后你手动对这个字典调用
Py_DECREF(),引用计数不会降到0(因为函数还持有它),但如果函数对象被回收(比如没有其他引用),字典的引用计数才会减少。
2. 场景二:无任何操作的脚本
这种情况下,脚本没有创建任何持有全局/局部字典的对象,所以:
- PyEval_EvalCode内部只会临时对传入的字典做
Py_INCREF()和Py_DECREF(),执行完毕后字典的引用计数会回到调用前的状态。
何时需要手动调用Py_DECREF?
根据不同的字典来源,分两种情况:
- 如果你自己创建了全局/局部字典(比如用
PyDict_New()):
不管脚本执行内容是什么,你都需要在不再使用这个字典时调用Py_DECREF()——但要注意:如果脚本创建了持有该字典的对象(比如函数、类),你需要确保这些对象已经被回收后再释放字典,否则会出现悬垂指针(字典被销毁,但还有对象引用它)。 - 如果你使用的是Python自带的字典(比如模块的全局字典、当前帧的全局字典):
这些是借用引用,你不需要手动调用Py_DECREF(),除非你之前显式调用了Py_INCREF()来持有它。
避免悬垂问题的关键注意点
- 不要提前释放还有其他对象引用的字典:比如脚本定义了函数,函数还在被使用时,不要释放它的
__globals__指向的字典。 - 不要对借用的引用调用
Py_DECREF():比如从PyModule_GetDict()拿到的模块字典,如果你没Py_INCREF()过,调用Py_DECREF()会直接导致字典被错误销毁,引发悬垂。 - 可以用
Py_REFCNT()宏(调试时)查看字典的引用计数变化,确认是否有额外的引用被持有。
内容的提问来源于stack exchange,提问作者HelloWorld
相关产品推荐
相关产品推荐

