【问题标题】:How to `pop` element from PyListObject?如何从 PyListObject 中“弹出”元素?
【发布时间】:2019-02-16 09:03:28
【问题描述】:

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

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

所以我的问题是,我如何使用 C-API pop 来自 PyListObject 的元素。

【问题讨论】:

    标签: python python-3.x list cpython python-c-api


    【解决方案1】:

    不,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 扩展无法(轻松)访问的函数,它还处理从特定索引(甚至是负数)弹出。就我个人而言,我什至不会费心重新实现它,只需使用 PyObject_CallMethod 调用 pop 方法:

    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);
    }
    

    这也可以根据您的用例进行调整。

    【讨论】:

      【解决方案2】:

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

      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-&gt;ob_size 的宏,我们在执行pop 时会减少它。

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

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

      正如@MSeifert 所指出的,这个版本不会改变底层数组的大小,就像list.pop() 会做的一样(如果在pop 之后只使用了一半或更少的底层数组)。这可以看作是上述实现的“特点”——用速度换内存。

      【讨论】:

      • 请注意,此实现不会像实际的list.pop 实现那样调整列表内部数组的大小。这可能会导致需要更多的内存。
      猜你喜欢
      • 2014-11-11
      • 1970-01-01
      • 2019-04-20
      • 2013-11-12
      • 2012-03-19
      • 1970-01-01
      • 2022-01-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多