0

I've been struggling to have some generic method of iterating over a sequence or iterable in the Cpython C API. The example code below compiles without errors, but fails when run as follows:

it ok
Segmentation fault: 11

I'm speculating that it's failing at the while ((item = PyIter_Next(it))) line but this is hardly controversial since the equivalent python is pretty standard:

x = [1,2,3]

it = iter(x)

for i in it:
    print(i)

Clearly, I'm doing something wrong with the c equivalent. Any help or guidance would be most appreciated!

#define PY_SSIZE_T_CLEAN
#include <Python.h>

int
main(int argc, char *argv[])
{
    wchar_t *program = Py_DecodeLocale(argv[0], NULL);
    if (program == NULL) {
        fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
        exit(1);
    }
    Py_SetProgramName(program);
    Py_Initialize();

    // create list
    PyObject *list = PyList_New(2);
    PyObject *n1 = PyLong_FromLong(1);
    PyObject *n2 = PyLong_FromLong(2);
    PyList_Append(list, n1);
    PyList_Append(list, n2);

    PyObject *it = PyObject_GetIter(list);
    PyObject *item;
    int i = 0;

    if (it) {
        printf("it ok\n");
        while ((item = PyIter_Next(it))) {
            if (PyLong_Check(item)) {
                i++;
                long long_item = PyLong_AsLong(item);
                printf("%d long: %ld", i, long_item);
            } else if PyFloat_Check(item) {
                i++;
                float float_item = PyFloat_AsDouble(item);
                printf("%d float: %f", i, float_item);
            } else if PyUnicode_Check(item) {
                i++;
                const char *unicode_item = PyUnicode_AsUTF8(item);
                printf("%d unicode: %s", i, unicode_item);
            } else continue;

        Py_DECREF(item);
        Py_DECREF(it);
        }

    } else {
        if (PyErr_Occurred()) {
            PyErr_Print();
        }
        Py_DECREF(n1);
        Py_DECREF(n2);
        Py_DECREF(list);

        if (Py_FinalizeEx() < 0) {
            exit(120);
        }
        PyMem_RawFree(program);
        return 0;
    }
}

In response to @DavidW's helpful observation of my error, here is the corrected code which could perhaps be a helpful example to others.

#define PY_SSIZE_T_CLEAN
#include <Python.h>

int
main(int argc, char *argv[])
{
    wchar_t *program = Py_DecodeLocale(argv[0], NULL);
    if (program == NULL) {
        fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
        exit(1);
    }
    Py_SetProgramName(program);
    Py_Initialize();

    // create list
    PyObject *list = PyList_New(0); // empty list
    PyObject *n1 = PyLong_FromLong(10);
    PyObject *n2 = PyLong_FromLong(20);
    PyList_Append(list, n1);
    PyList_Append(list, n2);

    PyObject *iter;
    PyObject *item;
    int i = 0;

    if ((iter = PyObject_GetIter(list)) == NULL) {
        return -1;
    }

    while ((item = PyIter_Next(iter)) != NULL) {
        if (PyLong_Check(item)) {
            i++;
            long long_item = PyLong_AsLong(item);
            printf("%d long: %ld\n", i, long_item);
        }

        if PyFloat_Check(item) {
            i++;
            float float_item = PyFloat_AsDouble(item);
            printf("%d float: %f\n", i, float_item);
        }

        if PyUnicode_Check(item) {
            i++;
            const char *unicode_item = PyUnicode_AsUTF8(item);
            printf("%d unicode: %s\n", i, unicode_item);
        }

        Py_DECREF(item);
    }

    Py_DECREF(iter);
    Py_DECREF(n1);
    Py_DECREF(n2);
    Py_DECREF(list);

    if (Py_FinalizeEx() < 0) {
        exit(120);
    }
    PyMem_RawFree(program);
    return 0;
}
shakfu
  • 41
  • 1
  • 5

1 Answers1

0
PyObject *list = PyList_New(2);

This creates a list with length 2, and with uninitialized contents (probably NULL pointers, but I'm not 100% sure).

PyList_Append(list, n1);
PyList_Append(list, n2);

These append to the end of the list (i.e. positions 3 and 4).

Therefore you have a list of <garbage>, <garbage>, n1, n2.

You either replace the PyList_Append with PyList_SetItem, or replace PyList_New(2) with PyList_New(0).


Py_DECREF(it);

should be done once the iteration has finished, not in the loop.

DavidW
  • 29,336
  • 6
  • 55
  • 86
  • Thanks very much, you are exactly right. What an embarrassingly silly error (and on my first post too). O well. – shakfu May 11 '20 at 21:24