如何从PyListObject中“pop”元素?

问题描述 投票:4回答:2

让我们说我有一个PyListObject,我想附加一个PyObject然后我可以使用PyList_Append API记录的List Objects C-API API。但是对于我的用例,我想要pop中的一个元素来自PyListObject(即python层中的my_list.pop())。

但是List Objects C-API文档没有提到有关pop操作的任何内容。

那么有关于PyListPop API函数的文档吗?

python python-3.x list cpython python-c-api
2个回答
5
投票

不,list.pop方法不能通过PyListObjects上的C-API直接获得。

鉴于list.pop已经存在并且在C中实现,您可以简单地查找CPython实现的功能:

static PyObject *
list_pop_impl(PyListObject *self, Py_ssize_t index)
{
    PyObject *v;
    int status;

    if (Py_SIZE(self) == 0) {
        /* Special-case most common failure cause */
        PyErr_SetString(PyExc_IndexError, "pop from empty list");
        return NULL;
    }
    if (index < 0)
        index += Py_SIZE(self);
    if (index < 0 || index >= Py_SIZE(self)) {
        PyErr_SetString(PyExc_IndexError, "pop index out of range");
        return NULL;
    }
    v = self->ob_item[index];
    if (index == Py_SIZE(self) - 1) {
        status = list_resize(self, Py_SIZE(self) - 1);
        if (status >= 0)
            return v; /* and v now owns the reference the list had */
        else
            return NULL;
    }
    Py_INCREF(v);
    status = list_ass_slice(self, index, index+1, (PyObject *)NULL);
    if (status < 0) {
        Py_DECREF(v);
        return NULL;
    }
    return v;
}

Source for CPython 3.7.2

这包括很多不能(容易)访问C扩展的函数,它还处理来自特定索引(甚至是负索引)的弹出。我个人甚至懒得重新实现它,但只需用pop调用PyObject_CallMethod方法:

PyObject *
list_pop(PyObject *lst){
    return PyObject_CallMethod(lst, "pop", "n", Py_SIZE(lst) - 1);
}

它可能比重新实现慢一点但它应该“更安全” - 不能意外地弄乱列表对象的不变量(例如调整大小条件)。

Cython中有另一种实现方式

static CYTHON_INLINE PyObject* __Pyx_PyList_Pop(PyObject* L) {
    /* Check that both the size is positive and no reallocation shrinking needs to be done. */
    if (likely(PyList_GET_SIZE(L) > (((PyListObject*)L)->allocated >> 1))) {
        Py_SIZE(L) -= 1;
        return PyList_GET_ITEM(L, PyList_GET_SIZE(L));
    }
    return CALL_UNBOUND_METHOD(PyList_Type, "pop", L);
}

这也适用于您的用例。


1
投票

你必须自己推出它。这是一个可能的实现(没有错误检查):

PyObject *my_pop_from_list(PyListObject *lst){
    //TODO: check lst isn't empty
    Py_SIZE(lst) -= 1;                                 // forget last element 
    return PyList_GET_ITEM(lst, PyList_GET_SIZE(lst)); // return last element
}

Py_SIZE只是一个访问lst->ob_size的宏,我们在执行pop时减少了。

还使用了没有错误检查的版本,即PyList_GET_ITEMPyList_GET_SIZE,因为一旦建立(参见TODO-comment),列表不为空 - 没有什么可能出错。

调用者接收一个新的引用,尽管PyList_GET_ITEM返回一个借用的引用:按照我们在上面的代码中的方式减小列表的大小,使列表“忘记”引用而不减少引用计数器。

正如@MSeifert指出的那样,这个版本不会改变底层数组的大小,就像list.pop()一样(如果在pop之后只使用底层数组的一半或更少)。这可以被视为上述实现的“特征” - 内存的交易速度。

© www.soinside.com 2019 - 2024. All rights reserved.