- 作者:
- 分类:知识&开发->语言->Python
- 阅读:4271
- 点赞:23
- 版权: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(), 是借用,引用不会+1PyImport_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.ccustom3.c``custom4.cstatic intCustom_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;}
