@dream-cheny
2016-04-15T17:00:19.000000Z
字数 7539
阅读 9533
python
一句话:为了在适当的时候回收 object 所占用的内存空间。
什么动态申请内存呐,什么内存泄漏呐,相关软文太多太多了,这里只讨论 python 如何使用和管理 object 的引用计数。
有两个宏 Py_INCREF(op) 和 Py_DECREF(op) 被用于 递增 和 递减 引用计数 reference counts。当引用计数到达0时,Py_DECREF() 会释放对象,但它不会直接调用 free(),相反,它会通过一个在 PyObject 中的类型对象 ob_type 的函数指针来调用 free()。出于这种目的,每一个对象都包含一个指向它的类型对象的指针。
看代码会更清晰:
typedef void (*destructor)(PyObject *)typedef struct _typeobject {...destructor tp_dealloc;...} PyTypeObject;
/* PyObject_HEAD defines the initial segment of every PyObject. */#define PyObject_HEAD \Py_ssize_t ob_refcnt; \struct _typeobject *ob_type;typedef struct _object {PyObject_HEAD} PyObject;
Py_DECREF()通过函数指针tp_dealloc来释放对象。
在 python 中,你不能拥有一个对象,但你可以拥有对象的引用。对象的引用数 reference count 定义为所有引用它的数量。一个引用的所有者有责任在引用不再需要的时候调用 Py_DECREF(),
引用的所有权可以被传递。
你有三种方式处理掉一个引用:
1. 将引用传递给别人
2. 将引用存储起来
3. 调用 Py_DECREF()
一个引用的所有者千万不要忘记处理它的引用。
为了防止内存泄露,每一次调用Py_INCREF(),有责任对应的调用一次Py_DECREF()。这种责任可以在函数之前传递(就是将引用传递给别人)。
绝大多数 Python 对象是通过调用 python/c api 创建的。
这些 api 函数的原型:
PyObject* Py_Something(arguments)
这些函数通常(但不必须)在返回一个新对象之前调用 Py_INCREF()。一般调用 Py_Something 的函数有责任去调用 Py_DECREF()。反过来,函数返回对象给调用者,同时也传递了引用的所有权给调用者。
Py_Something 创建一个引用,并将管理引用的责任传递给它的调用者,这被称为 new reference (我翻译成:实引用,即这个引用是被保护的)。
在 python 文档中,一般是这样体现的:
PyObject* PyList_New(int len)Return value: New reference.Returns a new list of length len on success, or NULL on failure.
New reference传达了两个密切相关的信息:
1.PyList_New会返回一个指向 PyObject 的指针(引用)
2. 我们有责任 DECREF 它
来,上代码:
void MyCode(arguments) {PyObject *pyo;...pyo = Py_Something(args);...Py_DECREF(pyo);}
MyCode需要负责管理Py_Something传递回来的对象的引用。
另一方面,如果 MyCode 需要返回 pyo,则不能调用 Py_DECREF:
void MyCode(arguments) {PyObject *pyo;...pyo = Py_Something(args);...return pyo;}
这就是将引用传递给别人,丢出去之后,可以不管引用的死活了。
如果一个函数返回 None,则 C 代码应该是这样的:
Py_INCREF(Py_None);return Py_None;
但有时 python 代码不需要调用 Py_DECREF():
PyObject * PyTuple_GetItem(register PyObject *op, register int i){if (!PyTuple_Check(op)) {PyErr_BadInternalCall();return NULL;}if (i < 0 || i >= ((PyTupleObject *)op) -> ob_size) {PyErr_SetString(PyExc_IndexError,"tuple index out of range");return NULL;}return ((PyTupleObject *)op) -> ob_item[i];}
这样情况被称为
borrowed reference,即这个引用是无保护的,此时不应该调用Py_DECREF()。
指向一个对象的引用也可能是一个 borrowed reference (我翻译成:弱引用)。
这个很难用语言来表达,我的理解是:就像一个人在网盘上共享了一部电影,你可以通过一个链接(引用)操作这部电影,当你在线看得正起劲的时候,duang,电影的主人删除它,你这边心理上就当机了。
实际上, borrowed reference 真实存在这个问题("当机"),使用一个弱引用的时候,这个弱引用所引用的对象被它的所有者处理掉了,这里就可能会发生内存 crash 。
borrowed reference的所有者不应该调用Py_DECREF()。
当然你可以把电影下载下来,变成你自己的(这就是下面要交待的)。
borrowed reference 可以变成一个 owned reference(我翻译成:实引用),通过调用 Py_INCREF()。这样并不会改变原始 borrowed reference 所有者的状态(而是创建一个新的 owned reference)。
当然 borrowed reference 也是有好处的,它不需要关心在代码流中处理引用,换句话说,使用 borrowed reference 在函数退出时不会出现内存泄露。
在 python 文档的,弱引用的表现形式:
PyObject* PyTuple_GetItem(PyObject *p, Py_ssize_t pos)Return value: Borrowed reference.
示例:
long sum_sequence(PyObject *sequence){int i, n;long total = 0;PyObject *item;n = PySequence_Length(sequence);if (n < 0)return -1; /* Has no length. *//* Caller should use PyErr_Occurred() if a -1 is returned. */for (i = 0; i < n; i++) {/* PySequence_GetItem INCREFs item. */item = PySequence_GetItem(sequence, i);if (item == NULL)return -1; /* Not a sequence, or other failure */if (PyInt_Check(item))total += PyInt_AsLong(item);Py_DECREF(item);}return total;}
这个函数的作用是:计算整型 list 的所有 items 的和。
对于每一个指向一个对象的指针的局部变量,不需要递增对象的引用计数。理论上,当有一个变量指向对象的时候,对象的引用计数会被+1,同时在变量离开作用域时,对象的引用计数会被-1,而这两个操作是相互抵消的,最终对象的引用数没有改变。
使用引用计数的唯一真正的原因是:当我们的变量指向一个对象时,防止这个对象被销毁。如果我们知道至少有一个其它的引用指向对象,直到我们的变量被销毁,那么就不需要临时递增对象的引用。
这出现在一个重要的应用场景中,如果对象作为参数传递给出由 python 调用的外部模块的 c functions, 调用机制保证在函数调用期间,对每一个参数都保留一个引用。
long sum_list(PyObject *list){int i, n;long total = 0;PyObject *item;n = PyList_Size(list);if (n < 0)return -1; /* Not a list *//* Caller should use PyErr_Occurred() if a -1 is returned. */for (i = 0; i < n; i++) {/* PyList_GetItem does not INCREF "item"."item" is unprotected. */item = PyList_GetItem(list, i); /* Can't fail */if (PyInt_Check(item))total += PyInt_AsLong(item);}return total;}
不要让一个对象处理未保护的状态 borrowed reference,如果对象处理未保护状态,它随时可能会被销毁。
borrowed reference 可能会引起微妙的 bug
一个普通的例子:从一个 list 获取对象,继续操作它,但并不递增它的引用。PyList_GetItem 会返回一个 borrowed reference ,所以 item 处于未保护状态。一些其他的操作可能会从 list 中将这个对象删除(递减它的引用计数,或者释放它)。导致 item 成为一个悬垂指针。
bug(PyObject *list) {PyObject *item = PyList_GetItem(list, 0);PyList_SetItem(list, 1, PyInt_FromLong(0L));PyObject_Print(item, stdout, 0); /* BUG! */}
no_bug(PyObject *list) {PyObject *item = PyList_GetItem(list, 0);Py_INCREF(item); /* Protect item. */PyList_SetItem(list, 1, PyInt_FromLong(0L));PyObject_Print(item, stdout, 0);Py_DECREF(item);}
到目前为止,我知道函数可以返回一个对象的引用,那么,当传递一个对象给函数的时候,会发生什么事情呢?
int Caller(void) {PyObject* pyo;Function(pyo);...}
绝大多数函数 假设 传递进来的参数已经被保护,因此在函数内部不需要调用 Py_INCREF(),除非函数想要参数存活到函数调用退出。
python 文档这样说:
When you pass an object reference into anotherfunction, in general, the function borrows the reference from you-- if it needs to store it, it will use Py_INCREF() to become anindependent owner.
PyDict_SetItem() 正是这样行为的一个例子。放置某些东西到字典中存储起来,因为它会 INCREF key 和 value。
恰好有两个重要的函数不会理会上面讨论的正常规则:PyTuple_SetItem() 和 PyList_SetItem()。
它们接管传递给他们的对象(即使是执行失败),python 文档使用了一个短语来表达接管的含义:偷取一个引用。
PyTuple_SetItem() 的原型:
int PyTuple_SetItem(PyObject *p, Py_ssize_t pos, PyObject *o)
调用它:PyTuple_SetItem(atuple, i, item), 是怎么做的呢?
如果 atuple[i] 当前包含一个 PyObject,那么 PyObject 会被 DECREF,然后 atuple[i] 被设置成 item,但 item 不会被 INCREF。
如果
PyTuple_SetItem调用失败,它会递减 item 的引用计数。同样,PyTuple_GetItem()也不会递增返回的 item 的引用计数。
隐含地,PyTuple_SetItem 会从你手中抓取 item 的引用,如果 item 没有被保护,那么 item 可能会被 DECREF,同时 python 就会 crash。这就是弱引用埋下的雷。
来看一段代码:
PyObject *t;PyObject *x;x = PyInt_FromLong(1L);PyTuple_SetItem(t, 0, x);
执行完第 3 条语句的时候,x 的引用数为1,如果不需要它了,正常应该调用 Py_DECREF(x),但调用了 PyTuple_SetItem,你就不能 DECREF x 了,因为当 tuple t 被 DECREF时,x 也会被 DECREF。
那么为什么 PyTuple_SetItem 会被设计成接管 item 的引用呢?
因为一个非常普遍的习惯是用新创建的对象填充一个 tuple 或者是 list。例如,可以像这样创建一个 tuple (1, 2, "three")(这里不考虑错误处理):
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"));
这就是 python 的极简主义,一名话
I like that。
另:PyTuple_SetItem 是创建 tuple 的唯一途径,因为 tuple 是不可变的数据类型。
可以用与上面同样风格的代码创建一个 list,当然要用 PyList_New() 和 PyList_SetItem()。与 tuple 不同的是,list 可以用 PySequence_SetItem() 创建,不过规则会有所不同:
PyObject *l, *x;l = PyList_New(3);x = PyInt_FromLong(1L);PySequence_SetItem(l, 0, x); Py_DECREF(x);x = PyInt_FromLong(2L);PySequence_SetItem(l, 1, x); Py_DECREF(x);x = PyString_FromString("three");PySequence_SetItem(l, 2, x); Py_DECREF(x);
PySequence_SetItem不会 “偷取” x 的引用,但会递增。
怎么看起来代码越来越多了,说好的极简主义呢?别急,往下看:
PyObject *t, *l;t = Py_BuildValue("(iis)", 1, 2, "three");l = Py_BuildValue("[iis]", 1, 2, "three");
PyObject* MyFunction(void){PyObject* temporary_list=NULL;PyObject* return_this=NULL;temporary_list = PyList_New(1); /* Note 1 */if (temporary_list == NULL)return NULL;return_this = PyList_New(1); /* Note 1 */if (return_this == NULL) {Py_DECREF(temporary_list); /* Note 2 */return NULL;}Py_DECREF(temporary_list); /* Note 2 */return return_this;}
Note1:
PyList_New返回的对象的引用数为1。
Note2:在MyFunction退出之后,temporary_list将要消失,所以它必须被 DECREF。
PyObject* MyFunction(void){PyObject* temporary=NULL;PyObject* return_this=NULL;PyObject* tup;PyObject* num;int err;tup = PyTuple_New(2);if (tup == NULL)return NULL;err = PyTuple_SetItem(tup, 0, PyInt_FromLong(222L)); /* Note 1 */if (err) {Py_DECREF(tup);return NULL;}err = PyTuple_SetItem(tup, 1, PyInt_FromLong(333L)); /* Note 1 */if (err) {Py_DECREF(tup);return NULL;}temporary = PyTuple_Getitem(tup, 0); /* Note 2 */if (temporary == NULL) {Py_DECREF(tup);return NULL;}return_this = PyTuple_Getitem(tup, 1); /* Note 3 */if (return_this == NULL) {Py_DECREF(tup);/* Note 3 */return NULL;}/* Note 3 */Py_DECREF(tup);return return_this;}
Note1:如果
PyTuple_SetItem失败,或者它创建的 tuple 被 DECREF 到 0,PyInt_FromLong返回的对象会被 DECREF。
Note2:PyTuple_Getitem没有递增返回对象的引用数。
Note3:不需要 DECREF 临时对象。
static PyObject *sys_getrefcount(PyObject *self, PyObject *args){PyObject *arg;if (!PyArg_ParseTuple(args, "O:getrefcount", &arg))return NULL;return PyInt_FromLong(arg->ob_refcnt);}
arg 是一个未保护对象,它不应该被 DECREF,因为它从来没有被 INCREF。