3

I am new to the Python/C API and while I got some basic functions to work, I am struggling with this one.

PyObject* sum_elements(PyObject*, PyObject *o) 
{
    Py_ssize_t n = PyList_Size(o);
    long total = 0;
    if (n < 0)
    {
        return PyLong_FromLong(total);
    }
    PyObject* item;
    for (int i = 0; i < n; i++) 
    {
        item = PyList_GetItem(o, i);
        if (!PyLong_Check(item)) continue;
        total += PyLong_AsLong(item);
    }
    return PyLong_FromLong(total);
}

Basically this is the function from the introduction on the doc page. It should receive a python list and return the sum of all elements. The function works fine if i pass a list, if I pass something else however i get the error message

SystemError: c:\_work\5\s\objects\listobject.c:187: bad argument to internal function

This situation should be handled by the if (n<0) statement, as n is -1 if the passed object is not a list.

I am binding the function the following way:

static PyMethodDef example_module_methods[] = {
    { "sum_list", (PyCFunction)sum_elements, METH_O, nullptr},
    { nullptr, nullptr, 0, nullptr }
};

Thanks.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
kleka
  • 364
  • 3
  • 14

1 Answers1

6

The error

SystemError: c:\_work\5\s\objects\listobject.c:187: bad argument to internal function

is actually occurs at

Py_ssize_t n = PyList_Size(o)

Because PyList_Size has an extra check to see whether the object of list type, If not it will call PyErr_BadInternalCall API to raise the SystemError. See the implementation of PyList_Size in listobject.c

PyList_Size(PyObject *op)
{
    if (!PyList_Check(op)) {
        PyErr_BadInternalCall();
        return -1;
    }
    else
        return Py_SIZE(op);
}

The PyErr_BadInternalCall a shorthand for PyErr_SetString(PyExc_SystemError, message), where message indicates that an internal operation (e.g. a Python/C API function) was invoked with an illegal argument.

You should use PyList_Check API to check whether the object is of list type . As per the doc it Return true if object is a list object or an instance of a subtype of the list type.

PyObject* sum_elements(PyObject*, PyObject *o) 
{    
    // Check if `o` is of `list` type, if not raise `TypeError`.
    if (!PyList_Check(o)) {
         PyErr_Format(PyExc_TypeError, "The argument must be of list or subtype of list");
         return NULL;
    }
    // The argument is list type, perform the remaining calculations.
    Py_ssize_t n = PyList_Size(o);
    long total = 0;
    if (n < 0)
    {
        return PyLong_FromLong(total);
    }
    PyObject* item;
    for (int i = 0; i < n; i++) 
    {
        item = PyList_GetItem(o, i);
        if (!PyLong_Check(item)) continue;
        total += PyLong_AsLong(item);
    }
    return PyLong_FromLong(total);
}

Once this extra check is added, the function call will raise

TypeError: The argument must be of list or sub type of list

when the argument other than list type is supplied.

Abdul Niyas P M
  • 18,035
  • 2
  • 25
  • 46
  • Thanks a lot, works perfectly. But I still don't understand, where the error occurs in my code? Shouldn't it just return zero in my case? – kleka Jul 18 '19 at 09:04
  • @Klemens Kasseroller `SystemError: c:\_work\5\s\objects\listobject.c:187: bad argument to internal function` occur at `Py_ssize_t n = PyList_Size(o);`. If you check out the [`PyList_Size`](https://github.com/python/cpython/blob/master/Objects/listobject.c#L219) implementation you could see that it check whether the object of list type if not call [`PyErr_BadInternalCall`](https://github.com/python/cpython/blob/762f93ff2efd6b7ef0177cad57939c0ab2002eac/Python/errors.c#L916) – Abdul Niyas P M Jul 18 '19 at 09:15