Обновление элементов массива с использованием API Python3 / C - PullRequest
0 голосов
/ 06 января 2019

У меня есть метод модуля, который берет список Python, а затем выводит тот же список со всеми элементами, умноженными на 100.

Я пытался следовать введению C здесь как можно ближе, но все еще сталкиваюсь с проблемами.

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

Любая помощь приветствуется.

1 Ответ

0 голосов
/ 06 января 2019

Итак, у вас есть ряд проблем, которые нужно исправить. Я перечислил их все под отдельными заголовками, чтобы вы могли просматривать их по одному.

Всегда возвращаюсь listObj

Когда вы получите ошибку в цикле for, вы goto выдадите метку ошибки, которая все еще возвращала список. Возвращая этот список, вы скрываете, что в вашей функции произошла ошибка. Вы должны всегда возвращать NULL, когда ожидаете, что ваша функция вызовет исключение.

Не увеличивается listObj ref count при возврате

Когда ваша функция вызывается, вы получаете заимствованную ссылку на ваши аргументы. Когда вы возвращаете один из этих аргументов, вы создаете новую ссылку на свой список, и поэтому должен увеличить счетчик ссылок. В противном случае интерпретатор будет иметь счетчик ссылок, который на единицу меньше, чем количество фактических ссылок на объект. Это приведет к ошибке, когда интерпретатор освобождает ваш список, когда есть только 1 ссылка, а не 0! Это может привести к ошибке сегмента или в худшем случае к случайным частям доступа к программе, которая с тех пор была освобождена и выделена для какого-либо другого объекта.

Использует PyObject_SetItem с примитивом

PyObject_SetItem можно использовать с dicts и другим классом, который реализует obj[key] = val. Поэтому вы не можете предоставить ему аргумент типа Py_ssize_t. Вместо этого используйте PyList_SetItem, который принимает в качестве аргумента индекса только Py_ssize_t.

Плохая обработка памяти item и incremented_item

PyObject_SetItem и PyList_SetItem оба обрабатывают уменьшение счетчика ссылок объекта, который уже находился в позиции, которая была установлена. Поэтому нам не нужно беспокоиться об управлении счетчиком ссылок item, поскольку мы работаем только со ссылкой , заимствованной из списка. Эта пара функций также ворует ссылку на incremented_item, поэтому нам не нужно беспокоиться об управлении ее счетчиком ссылок.

Утечка памяти при неверных аргументах

Например, когда вы вызываете вашу функцию с помощью int. Вы создадите новую ссылку на объект int 100, но поскольку вы 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.

...