如何从 PyListObject 中“弹出”元素?
How to `pop` element from PyListObject?
假设我有一个 PyListObject
,我想附加一个 PyObject
然后我可以使用 PyList_Append
API,它记录在 List Objects C-API .但是对于我的用例,我想 pop
来自 PyListObject
的元素(即 python 层中的 my_list.pop()
)。
但是 List Objects C-API 文档没有提及任何关于 pop
操作的内容。
所以我的问题是,如何使用 C-API.
pop
来自 PyListObject
的元素
您将不得不自己推出。这是一个可能的实现(没有错误检查):
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_ITEM
和 PyList_GET_SIZE
,因为一旦建立(参见 TODO 注释),列表就不是空的 - 什么都不能去错了。
调用者收到一个新的引用,尽管 PyList_GET_ITEM
returns 是借来的:按照我们在上面代码中所做的方式减小列表的大小,使列表 "forget"引用而不减少引用计数器。
正如@MSeifert 指出的那样,此版本不会更改底层数组的大小,与 list.pop()
的方式相同(如果在 pop 后仅使用一半或更少的底层数组) .这可以看作是上述实现的 "feature" - 内存交易速度。
不,list.pop
方法不能通过 PyListObject
s 上的 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;
}
这包括许多 C 扩展无法(轻松)访问的函数,它还处理从特定索引(甚至是负索引)弹出的问题。就我个人而言,我什至懒得重新实现它,只需使用 PyObject_CallMethod
:
调用 pop
方法
PyObject *
list_pop(PyObject *lst){
return PyObject_CallMethod(lst, "pop", "n", Py_SIZE(lst) - 1);
}
它可能比重新实现慢一点,但应该是 "safer" - 不能意外地弄乱列表对象的不变量(例如调整大小条件)。
另一个实现出现在 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);
}
这也可以根据您的用例进行调整。
假设我有一个 PyListObject
,我想附加一个 PyObject
然后我可以使用 PyList_Append
API,它记录在 List Objects C-API .但是对于我的用例,我想 pop
来自 PyListObject
的元素(即 python 层中的 my_list.pop()
)。
但是 List Objects C-API 文档没有提及任何关于 pop
操作的内容。
所以我的问题是,如何使用 C-API.
pop
来自 PyListObject
的元素
您将不得不自己推出。这是一个可能的实现(没有错误检查):
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_ITEM
和 PyList_GET_SIZE
,因为一旦建立(参见 TODO 注释),列表就不是空的 - 什么都不能去错了。
调用者收到一个新的引用,尽管 PyList_GET_ITEM
returns 是借来的:按照我们在上面代码中所做的方式减小列表的大小,使列表 "forget"引用而不减少引用计数器。
正如@MSeifert 指出的那样,此版本不会更改底层数组的大小,与 list.pop()
的方式相同(如果在 pop 后仅使用一半或更少的底层数组) .这可以看作是上述实现的 "feature" - 内存交易速度。
不,list.pop
方法不能通过 PyListObject
s 上的 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;
}
这包括许多 C 扩展无法(轻松)访问的函数,它还处理从特定索引(甚至是负索引)弹出的问题。就我个人而言,我什至懒得重新实现它,只需使用 PyObject_CallMethod
:
pop
方法
PyObject *
list_pop(PyObject *lst){
return PyObject_CallMethod(lst, "pop", "n", Py_SIZE(lst) - 1);
}
它可能比重新实现慢一点,但应该是 "safer" - 不能意外地弄乱列表对象的不变量(例如调整大小条件)。
另一个实现出现在 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);
}
这也可以根据您的用例进行调整。