我有一个模块方法,它接受一个 python 列表,然后输出相同的列表,其中所有项目乘以 100。
我尝试关注the C intro here尽可能接近,但仍然遇到问题。
static PyObject *
test_update_list(PyObject *self, PyObject *args)
{
PyObject *listObj = NULL;
PyObject *item = NULL;
PyObject *mult = PyLong_FromLong(100);
PyObject *incremented_item = NULL;
if (!PyArg_ParseTuple(args, "O", &listObj))
{
return NULL;
}
/* get the number of lines passed to us */
Py_ssize_t numLines = PyList_Size(listObj);
/* should raise an error here. */
if (numLines < 0) return NULL; /* Not a list */
for (Py_ssize_t i=0; i<numLines; i++) {
// pick the item
item = PyList_GetItem(listObj, i);
if (mult == NULL)
goto error;
// increment it
incremented_item = PyNumber_Add(item, mult);
if (incremented_item == NULL)
goto error;
// update the list item
if (PyObject_SetItem(listObj, i, incremented_item) < 0)
goto error;
}
error:
Py_XDECREF(item);
Py_XDECREF(mult);
Py_XDECREF(incremented_item);
return listObj;
};
上面的内容很好,但是当我在 ipython 中运行时,出现以下错误。
如果我取消错误处理,就会出现段错误。
---------------------------------------------------------------------------
SystemError Traceback (most recent call last)
SystemError: null argument to internal routine
The above exception was the direct cause of the following exception:
SystemError Traceback (most recent call last)
<ipython-input-3-da275aa3369f> in <module>()
----> 1 testadd.test_update_list([1,2,3])
SystemError: <built-in function ulist> returned a result with an error set
感谢任何帮助。
最佳答案
因此,您有许多问题需要纠正。我已将它们全部列在单独的标题下,以便您可以一次浏览它们。
始终返回 listObj
当你在 for 循环中遇到错误时,你会 goto
错误标签,仍然返回列表。通过返回此列表,您可以隐藏函数中存在错误的情况。您必须始终返回 NULL
当您期望函数引发异常时。
不增加 listObj
返回的引用计数
当你的函数被调用时,你会得到一个对你的参数的借用引用。当您返回这些参数之一时,您正在创建对列表的新引用,因此必须增加其引用计数。否则,解释器的引用计数将比对象的实际引用数低 1。这最终会导致一个错误,当只有 1 个引用而不是 0 个引用时,解释器会释放列表!这可能会导致段错误,或者在最坏的情况下可能导致程序访问的随机部分已被释放并分配给其他对象。
使用PyObject_SetItem
与原始
PyObject_SetItem
可以与字典和其他实现 obj[key] = val
的类一起使用。因此,您不能为其提供 Py_ssize_t
类型的参数。 。相反,使用 PyList_SetItem
只接受 Py_ssize_t
作为其索引参数。
item
的内存处理错误和incremented_item
PyObject_SetItem
和PyList_SetItem
两者都处理减少已经位于所设置位置的对象的引用计数。所以我们不需要担心管理item
的引用计数因为我们仅使用从列表中借用的引用。这对函数还窃取了对 incremented_item
的引用,因此我们也无需担心管理其引用计数。
参数不正确导致内存泄漏
例如,当您使用 int 调用函数时。您将创建一个对 100 int 对象的新引用,但是因为您return NULL
而不是goto error
,该引用将会丢失。因此,您需要以不同的方式处理此类情况。在我的解决方案中,我移动了 PyLong_FromLong
在 arg 和类型检查之后调用。这样,只有在保证会使用这个新的*对象时,我们才会创建它。
工作代码
旁注:我删除了 goto 语句,因为只剩下一个,因此当时进行错误处理比稍后进行更有意义。
static PyObject *
testadd_update_list(PyObject *self, PyObject *args)
{
PyObject *listObj = NULL;
PyObject *item = NULL;
PyObject *mult = NULL;
PyObject *incremented_item = NULL;
Py_ssize_t numLines;
if (!PyArg_ParseTuple(args, "O:update_list", &listObj))
{
return NULL;
}
if (!PyList_Check(listObj)) {
PyErr_BadArgument();
return NULL;
}
/* get the number of lines passed to us */
// Don't want to rely on the error checking of this function as it gives a weird stack trace.
// Instead, we use Py_ListCheck() and PyErr_BadArgument() as above. Since list is definitely
// a list now, then PyList_Size will never throw an error, and so we could use
// PyList_GET_SIZE(listObj) instead.
numLines = PyList_Size(listObj);
// only initialise mult here, otherwise the above returns would create a memory leak
mult = PyLong_FromLong(100);
if (mult == NULL) {
return NULL;
}
for (Py_ssize_t i=0; i<numLines; i++) {
// pick the item
// It is possible for this line to raise an error, but our invariants should
// ensure no error is ever raised. `list` is always of type list and `i` is always
// in bounds.
item = PyList_GetItem(listObj, i);
// increment it, and check for type errors or memory errors
incremented_item = PyNumber_Add(item, mult);
if (incremented_item == NULL) {
// ERROR!
Py_DECREF(mult);
return NULL;
}
// update the list item
// We definitely have a list, and our index is in bounds, so we should never see an error
// here.
PyList_SetItem(listObj, i, incremented_item);
// PyList_SetItem steals our reference to incremented_item, and so we must be careful in
// how we handle incremented_item now. Either incremented_item will not be our
// responsibility any more or it is NULL. As such, we can just remove our Py_XDECREF call
}
// success!
// We are returning a *new reference* to listObj. We must increment its ref count as a result!
Py_INCREF(listObj);
Py_DECREF(mult);
return listObj;
}
脚注:
* PyLong_FromLong(100)
实际上并不创建新对象,而是返回对现有对象的新引用。低值整数(0 <= i < 128
我认为)都会被缓存,并且在需要时返回相同的对象。这是一个实现细节,旨在避免对小值进行高级分配和释放整数,从而提高 Python 的性能。
关于python - 使用 Python3/C API 更新数组的元素,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54061159/