Python C扩展:PyList的Py_DECREF使用正确性咨询
Py_DECREF()使用完全正确,无需手动清理列表元素 首先可以肯定的是,你这段代码里对Py_DECREF()的处理完全符合Python C API的规范,不用额外去逐个递减列表元素的引用计数,核心原因和Python的引用计数管理逻辑直接相关:
列表会自动接管元素的引用:当你调用
PyList_Append(ret_obj, tmp_obj)时,Python列表内部会自动执行Py_INCREF(tmp_obj),把元素的引用计数加1。这意味着列表已经持有了该元素的有效引用,所以你在append之后立刻调用Py_DECREF(tmp_obj)是完全正确的——这一步是释放你创建临时对象时持有的那个引用,此时元素的引用计数会维持在1(由列表持有)。出错时只需清理列表本身:当
PyList_Append()失败(比如内存分配不足),你只需要调用Py_DECREF(ret_obj)就足够完成清理工作。因为当ret_obj的引用计数降到0时,Python的垃圾回收机制会自动遍历列表中的所有元素,逐个调用Py_DECREF()来释放它们的引用。如果元素的引用计数因此降到0,对应的字符串对象也会被自动销毁。要是你手动去遍历列表元素并调用
Py_DECREF(),反而会导致重复释放的问题——因为列表销毁时还会再一次对元素做DECREF,这会让元素的引用计数变成负数,触发内存错误。
我再拆解下你代码里单个元素的引用计数变化,帮你更直观理解:
tmp_obj = PyString_FromStringAndSize(...):创建字符串对象,引用计数为1(你持有这个引用)。PyList_Append(ret_obj, tmp_obj):列表内部对tmp_obj做Py_INCREF,引用计数变为2。Py_DECREF(tmp_obj):你释放自己的临时引用,引用计数回到1(此时只有列表持有这个元素的引用)。
当你在出错分支调用Py_DECREF(ret_obj)时,如果你是这个列表的唯一持有者(引用计数从1降到0),列表会被销毁,同时自动处理所有内部元素的引用释放,完全不需要你手动干预。
内容的提问来源于stack exchange,提问作者Sam Tatasurya

