@dream-cheny
2016-04-16T01:00:19.000000Z
字数 7539
阅读 9083
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 another
function, in general, the function borrows the reference from you
-- if it needs to store it, it will use Py_INCREF() to become an
independent 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。