Как «вытолкнуть» элемент из PyListObject? - PullRequest
0 голосов
/ 16 февраля 2019

Допустим, у меня есть PyListObject, и я хочу добавить PyObject, тогда я могу использовать PyList_Append API, который задокументирован в List Objects C-API.Но для моего случая использования я хочу pop элемент из PyListObject (то есть my_list.pop() в слое python).

Но документация C-API Списка объектов не делаетупомяните что-нибудь о pop операции.

Так есть ли какие-либо документы о PyListPop API функции?.

Ответы [ 2 ]

0 голосов
/ 16 февраля 2019

Нет, метод list.pop не доступен напрямую через C-API на PyListObject s.

Учитывая, что 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;
}

Источник для 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);
}

Это также может быть адаптировано для вашего варианта использования.

0 голосов
/ 16 февраля 2019

Вам придется раскатать это самостоятельно.Вот возможная реализация (без проверки ошибок):

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 возвращает заимствованную ссылку: уменьшение размера списка, как мы делали в коде выше, заставляет список "забыть" ссылку без уменьшения счетчика ссылок.

Как указывал @MSeifert, эта версия не меняет размер базового массива, как это делает list.pop() (если после pop используется только половина или меньше базового массива).Это можно рассматривать как «особенность» вышеописанной реализации - скорость торговли для памяти.

...