pythonc++python-c-api

How do I check if a PyObject is a list?


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.


Solution

  • 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 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 is 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.