python - 使用 Python3/C API 更新数组的元素

标签 python python-c-api

我有一个模块方法,它接受一个 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_SetItemPyList_SetItem两者都处理减少已经位于所设置位置的对象的引用计数。所以我们不需要担心管理item的引用计数因为我们仅使用从列表中借用的引用。这对函数还窃取了对 incremented_item 的引用,因此我们也无需担心管理其引用计数。

参数不正确导致内存泄漏

例如,当您使用 int 调用函数时。您将创建一个对 1​​00 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/

相关文章:

python - del_user() 缺少 1 个必需的位置参数 : 'username' - Django

python - 将嵌入式 Python IO 重定向到使用 AllocConsole Win32 应用程序创建的控制台

Python C 扩展向异常添加属性

c++ - 嵌入Python 3.3 : How do I access _PyParser_Grammar?

python - 无法在Python中导入C扩展文件

python - py安装程序: qwebkit cannot load pictures and garbled text when package pyside gui with pyinstaller

python - PyQt Phonon 后端音频效果 'speed' 的文档

python - 如果涉及抽象表,SQLAlchemy 索引会导致 "Can' t 将未命名列添加到列集合中

python - 随机 "[Errno -2] Name or service not known"错误

python - 使用 Python 的 C API 创建一个对象