You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 10:22:45