Python删除字面量引发其他字面量引用计数异常及C++嵌入场景的使用疑问
关于C++嵌入Python中PyLong对象引用计数的正确统计方法
交互式环境异常现象解释
你在交互式Python中遇到的引用计数异常,是解释器交互式环境的特殊行为导致的:
在第一个测试序列中,首次调用sys.getrefcount(3)触发的NameError会在异常栈中保留对字面量3的引用。当执行del(4)触发SyntaxError时,解释器会清理之前留存的异常帧,间接导致3的引用计数减少1,因此出现了看似无关的引用计数变化。这种情况仅存在于交互式环境的异常处理流程中,不会在C++嵌入的非交互式执行场景中复现。
异常场景复现代码:
Python 3.11.4 (tags/v3.11.4:d2340ef, Jun 7 2023, 05:45:37) [MSC v.1934 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> sys.getrefcount(3) Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'sys' is not defined >>> import sys >>> sys.getrefcount(3) 1000000052 >>> sys.getrefcount(4) 1000000085 >>> a = 3 >>> sys.getrefcount(3) 1000000053 >>> sys.getrefcount(4) 1000000085 >>> a = 4 >>> sys.getrefcount(3) 1000000052 >>> sys.getrefcount(4) 1000000086 >>> del(4) File "<stdin>", line 1 del(4) ^ SyntaxError: cannot delete literal >>> sys.getrefcount(3) 1000000051 >>> sys.getrefcount(4) 1000000086
符合预期的测试序列:
Python 3.11.4 (tags/v3.11.4:d2340ef, Jun 7 2023, 05:45:37) [MSC v.1934 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> sys.getrefcount(3) 1000000051 >>> sys.getrefcount(4) 1000000085 >>> a = 3 >>> sys.getrefcount(3) 1000000052 >>> sys.getrefcount(4) 1000000085 >>> a = 4 >>> sys.getrefcount(3) 1000000051 >>> sys.getrefcount(4) 1000000086 >>> del(4) File "<stdin>", line 1 del(4) ^ SyntaxError: cannot delete literal >>> sys.getrefcount(3) 1000000051 >>> sys.getrefcount(4) 1000000086
C++嵌入场景的正确做法
在C++中统计PyLong_FromLong(3)创建对象的引用计数,需要注意Python的小整数缓存机制,以及正确调用Python的C API:
1. 理解小整数缓存的影响
Python会缓存范围为-5到256的整数对象,因此PyLong_FromLong(3)不会创建新对象,而是返回缓存中的已有对象。这个缓存对象的引用计数包含解释器内部的所有引用,因此你看到的计数会远大于自己代码中的操作次数。如果需要测试自己创建的独立整数对象,可以使用超出缓存范围的数值(比如PyLong_FromLong(257))。
2. 调用sys.getrefcount的C++实现步骤
以下是在C++中调用sys.getrefcount统计对象引用计数的示例代码:
#include <Python.h> int main() { // 初始化Python解释器 Py_Initialize(); // 获取sys模块 PyObject* sys_module = PyImport_ImportModule("sys"); if (!sys_module) { PyErr_Print(); return 1; } // 获取sys.getrefcount函数 PyObject* getrefcount_func = PyObject_GetAttrString(sys_module, "getrefcount"); if (!getrefcount_func || !PyCallable_Check(getrefcount_func)) { PyErr_Print(); Py_DECREF(sys_module); return 1; } // 创建整数对象(3属于缓存对象,示例用257测试独立对象) PyObject* num_obj = PyLong_FromLong(257); if (!num_obj) { PyErr_Print(); Py_DECREF(getrefcount_func); Py_DECREF(sys_module); return 1; } // 调用getrefcount,注意返回值比实际引用计数多1(因为函数调用时增加了一次引用) PyObject* result = PyObject_CallFunction(getrefcount_func, "O", num_obj); if (result) { long ref_count = PyLong_AsLong(result); printf("引用计数: %ld\n", ref_count - 1); // 减去函数调用带来的额外引用 Py_DECREF(result); } else { PyErr_Print(); } // 清理资源 Py_DECREF(num_obj); Py_DECREF(getrefcount_func); Py_DECREF(sys_module); // 关闭Python解释器 Py_Finalize(); return 0; }
3. 关键注意事项
sys.getrefcount在调用时会将传入的对象作为参数增加一次引用,因此返回的计数需要减1才是实际的引用计数。- 对于小整数缓存对象,引用计数包含解释器内部的全局引用,数值会非常大,不适合用来调试自己代码的引用管理,建议使用非缓存范围内的整数测试。
- 严格遵循Python C API的引用计数规则:所有通过API创建或获取的PyObject指针,在使用完毕后必须调用
Py_DECREF释放,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Guido Gagliardi
相关产品推荐
相关产品推荐

