在Python源码及扩展模块中,对引用计数的使用有三种方式: 拥有引用、 借用引用和窃取引用。
拥有引用:拥有引用比较简单,针对PyObject对象调用Py_INCREF或Py_XINCREF即可,如将一个对象加入到字典中时,会分别对key和value调用Py_INCREF
对于创建类函数,对象创建成功后,在函数的内部会自动增加引用计数,此类函数有:
PyObject* PyLong_FromLong(long v)
PyObject* PyLong_FromDouble
PyObject* PyInt_FromLong(long ival)
PyObject* PyString_FromString(const char *v)
…
借用引用:当Python对象做为函数参数传递时,如果函数内部仅仅是对参数的使用,并不需要内部保留对对象的引用,此时在函数内部不需要对参数增加引用计数,函数内部对参数对象的使用是借用调用者对参数拥有的所有权,其借用调用者的所有权,故称为借用引用。
窃取引用:这个概念相对难以理解,窃取引用最常见的情况是函数PyList_SetItem() 和 PyTuple_SetItem(),为了更好说明,现将函数 PyTuple_SetItem()的源码贴出,在向元组添加对象时,并未增加新加入对象(newitem)的引用计数,但在清空或添加出现异常时,减少新添加对象的引用计数,就好像其已经拥有(调用过Py_INCREF)了该对象一样。这就像是PyTuple_SetItem函数从调用者那里窃取了对象的所有权一样,故此种现象称为“窃取引用”。
int
PyTuple_SetItem(register PyObject *op, register Py_ssize_t i, PyObject *newitem)
{
register PyObject *olditem;
register PyObject **p;
if (!PyTuple_Check(op) || op->ob_refcnt != 1) {
Py_XDECREF(newitem);
PyErr_BadInternalCall();
return -1;
}
if (i < 0 || i >= Py_SIZE(op)) {
Py_XDECREF(newitem);
PyErr_SetString(PyExc_IndexError,
“tuple assignment index out of range”);
return -1;
}
p = ((PyTupleObject *)op) -> ob_item + i;
olditem = *p;
*p = newitem;
Py_XDECREF(olditem);
return 0;
}
为何会有如此怪异的设计,Python用户手册说的很明白,如下所示:
Few functions steal references; the two notable exceptions are PyList_SetItem() and PyTuple_SetItem(),
which steal a reference to the item (but not to the tuple or list into which the item is put!).
These functions were designed to steal a reference because of a common idiom for populating a tuple or list with newly created objects;
for example, the code to create the tuple (1, 2, “three”) could look like this (forgetting about error handling for the moment;
a better way to code this is shown below):
PyObject *t;
t = PyTuple_New(3);
PyTuple_SetItem(t, 0, PyInt_FromLong(1L));
PyTuple_SetItem(t, 1, PyInt_FromLong(2L));
PyTuple_SetItem(t, 2, PyString_FromString(“three”));
原来,大多数情况下,这种写法最为简洁。