- 作者:
- 分类:知识&开发->语言->Python
- 阅读:2828
- 点赞:22
- 版权:CC BY-SA 4.0
- 创建:2021-01-21
- 更新:2021-01-21
原文链接(持续更新):https://neucrack.com/p/340
查看官方文档: https://docs.python.org/3/extending/extending.html#reference-counts
引用和借用介绍: https://docs.python.org/3/extending/extending.html#ownership-rules
GC 标记回收 注意点
注意借用周期必须要在引用周期内
所有新建对象比如
PyLong_FromLong()
、Py_BuildValue()
、PyDict_Keys()
、PyDict_Values()
都会将引用+1
, 同样,释放变量引用会自动-1
,比如Py_TYPE(self)->tp_free(***)
会自动减引用所以在我们使用临时变量时,也需要将其用变量存起来,用完后调用
Py_DECREF()
,比如下面用法是错误的:PyList_SetItem(list, 1, PyInt_FromLong(0L));
这里
PyInt_FromLong
新建对象引用+1
, 然后PyList_SetItem
将新对象引用+1
(后面有讲), 所以新对象共引用了2
次,但是临时变量用完后没有将引用-1
, 导致最后list
释放时,-1
了引用还大于零,即内存泄露,始终无法回收
正确用法:PyObject* temp = PyInt_FromLong(0L);
PyList_SetItem(list, 1, temp);
Py_DECREF(temp);
许多从其他对象中提取对象的函数也通过引用来转移所有权, 比如
PyObject_GetAttrString()
, 引用+1
从对象中提取对象,有些例外
PyTuple_GetItem()
,PyList_GetItem()
,PyDict_GetItem()
,and PyDict_GetItemString()
, 是借用,引用不会+1
PyImport_AddModule()
也是借用,引用不会+1
(因为引用可以在sys.modules
中)如果你将一个对象赋值给其它变量,比如函数返回,变量赋值,对象赋值, 这都是借用,如果你希望借用者变成引用者,需要手动调用
Py_INCREF()
,但是有两个意外情况PyTuple_SetItem()
和PyList_SetItem()
, 这两个会获得引用权(引用+1
), 注意PyDict_SetItem()
只是借用注意,如果用C写一个函数,返回值必须拥有引用权,而不是借用,这样才能保证调用方能使用这个对象, 所以如果你返回的对象是借来的,那么必须将其转为引用,即调用
Py_INCREF()
将引用+1
比如
Py_None
, 如果直接return Py_None
,则有可能出现错误,因为这是一个赋值行为,而且Py_None
不是新New
出来的, 所以是借用, 需要在返回前增加一次引用Py_INCREF(); return Py_None;
另外,在借用时也要注意,借用期间注意要保证引用不会被减到
0
,如果没法保证,就自己手动+1
,比如void bug(PyObject *list)
{
PyObject *item = PyList_GetItem(list, 0);
PyList_SetItem(list, 1, PyLong_FromLong(0L));
PyObject_Print(item, stdout, 0); /* BUG! */
}
这里看起来没什么问题,除了上面提到的
PyLong_FromLong
的问题, 只设置了列表元素1
,而没有改变元素0
,那么为什么会出bug
呢?
因为这里设置了元素1
,那么之前的元素1
就有可能引用为0
,可能会触发它的__del__
函数,用户如果在这个函数里面也删除了元素0
, 那我们在后面调用item
就会失败,因为这个对象有可能已经被回收了, 正确的方式是在设置元素1
之前给item
增加一个引用,然后再设置元素1
,print
结束后再删除item
的引用:void no_bug(PyObject *list)
{
PyObject *item = PyList_GetItem(list, 0);
PyObject* temp = PyLong_FromLong(0L);
Py_INCREF(item);
PyList_SetItem(list, 1, temp);
PyObject_Print(item, stdout, 0);
Py_DECREF(item);
Py_DECREF(temp);
}
如果不确定传入的变量是不是
NULL
, 使用Py_XINCREF()
和Py_XDECREF()
对于
PyArg_ParseTupleAndKeywords
解析的参数,如果获得的值是对象,需要对这个对象增加引用然后再使用,这样可以防止外部传进来的变量被销毁,可以参考Python
源码中的custom2.c
custom3.c``custom4.c
static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"first", "last", "number", NULL};
PyObject *first = NULL, *last = NULL, *tmp;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
&first, &last,
&self->number))
return -1;
if (first) {
tmp = self->first; // 保存旧变量
Py_INCREF(first); // 增加参数对象的引用
self->first = first; // 赋值
Py_DECREF(tmp); // 减少旧变量的引用
}
if (last) {
tmp = self->last;
Py_INCREF(last);
self->last = last;
Py_DECREF(tmp);
}
return 0;
}