Sartorible
Sartorible

Reputation: 356

Why might a C-based Python extension always return the same value?

The following code seems incredibly simple. an integer is passed to the function in Python, which creates a PyList in C then populates it:

hello.c:

#include <Python.h>

PyObject* getlist(int *len)
{
    printf("Passed to C: %d\n", *len);
    PyObject *dlist = PyList_New(*len);
    double num = 0.1;
    for (int i = 0; i < *len; i++)
    {
        PyList_SetItem(dlist, i, PyFloat_FromDouble(num));
        num += 0.1;
    }

    return dlist;
}

static char helloworld_docs[] =
   "Fill docs where possible\n";

static PyMethodDef helloworld_funcs[] = {
   {"getlist", (PyCFunction)getlist, METH_VARARGS, helloworld_docs},
   {NULL}
};

static struct PyModuleDef Helloworld =
{
    PyModuleDef_HEAD_INIT,
    "Helloworld", // module name
    "NULL", // module documentation
    -1,   /* size of per-interpreter state of the module, or -1 if the module keeps state in global variables. */
    helloworld_funcs
};

PyMODINIT_FUNC PyInit_helloworld(void)
{
    return PyModule_Create(&Helloworld);
}

setup.py:

from distutils.core import setup
from distutils.extension import Extension

setup(name='helloworld', 
      version='1.0', 
      ext_modules=[Extension('helloworld', ['hello.c'])])

usepkg.py:

#!/usr/bin/python
import sys
import helloworld
print("Input to Python:", sys.argv[1])
print (helloworld.getlist(sys.argv[1]))

I build and install using

python3 setup.py build
python3 setup.py install

and I see no errors.

The odd behaviour happens when I test it. For example:

python3 usepkg.py 4

No matter what value I give as an argument, the output is always the same:

Input to Python: 4
Passed to C: 6
[0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6]

The value passed to C is always 6. This is the same whether the input agument is int or Py_ssize_t. What am I missing?

Upvotes: 2

Views: 91

Answers (1)

anthony sottile
anthony sottile

Reputation: 69944

I'm quite surprised there's no warnings here when building, the types of functions shouldn't be their primitive types but of PyObject* -- you'll then parse the types and execute your function

Here's an adjustment to your function:

PyObject* getlist(PyObject* self, PyObject* args)
{
    int len;
    if (!PyArg_ParseTuple(args, "i", &len)) {
        return NULL;
    }
    printf("Passed to C: %d\n", len);
    PyObject *dlist = PyList_New(len);
    double num = 0.1;
    for (int i = 0; i < len; i++)
    {
        PyList_SetItem(dlist, i, PyFloat_FromDouble(num));
        num += 0.1;
    }

    return dlist;
}

More information on this can be found in the parsing arguments and building values documentation


The number you were getting was likely the value in PyObject*->ob_refcount of self (the number of references to the C module)

in my case I saw 4 instead of 6, though I'm likely using a different version of python and/or calling approach

Upvotes: 4

Related Questions