cpyobject

How do I use PyObject* args in C python extension?


I am trying to make a simple extension in C that should be able to extend python code . I found that code on https://github.com/munirhossain/py_c_extension

#include <Python.h>

// Function 1: A simple 'hello world' function
static PyObject* helloworld(PyObject* self, PyObject* args) 
{   
    printf("Hello Munir\n");
    Py_RETURN_NONE;
    return Py_None;
}

// Function 2: A C fibonacci implementation
// this is nothing special and looks exactly
// like a normal C version of fibonacci would look
int Cfib(int n)
{
    if (n < 2)
        return n;
    else
        return Cfib(n-1)+Cfib(n-2);
}
// Our Python binding to our C function
// This will take one and only one non-keyword argument
static PyObject* fib(PyObject* self, PyObject* args)
{
    // instantiate our `n` value
    int n;
    // if our `n` value 
    if(!PyArg_ParseTuple(args, "i", &n))
        return NULL;
    // return our computed fib number
    return Py_BuildValue("i", Cfib(n));
}

// Our Module's Function Definition struct
// We require this `NULL` to signal the end of our method
// definition 
static PyMethodDef myMethods[] = {
    { "helloworld", helloworld, METH_NOARGS, "Prints Hello Munir" },
    { "fib", fib, METH_VARARGS, "Computes Fibonacci" },
    { NULL, NULL, 0, NULL }
};

// Our Module Definition struct
static struct PyModuleDef myModule = {
    PyModuleDef_HEAD_INIT,
    "myModule",
    "Test Module",
    -1,
    myMethods
};

// Initializes our module using our above struct
PyMODINIT_FUNC PyInit_myModule(void)
{
    return PyModule_Create(&myModule);
}

I would like to modify that code like when I call the helloworld func , like helloworld("max") it returns Hello max in C , but idk how can I use PyObject* args :/ Any ideas how I can do that (in C) ?


Solution

  • You should read the PyArg_ParseTuple documentation. Basically this should work:

    static PyObject* helloworld(PyObject* self, PyObject* args) 
    {   
        const char *name;
    
        if (!PyArg_ParseTuple(args, "s", &name)) {
            return NULL;
        }
    
        printf("Hello %s\n", name);
        Py_RETURN_NONE;
    }
    

    and you need to change the method definition in the table to

    { "helloworld", helloworld, METH_VARARGS, "Prints Hello <name>" },
    

    naturally, as it now takes arguments. The description s says that the argument tuple must contain exactly one item and it should be of type str; it is converted to UTF-8 (each CPython string object can contain a cached copy of the string content in UTF-8 for C use), and a pointer to the first character is stored into the pointer object pointed to by the corresponding argument in the variable argument list (i.e. the &name - the output value is const char *, and the corresponding argument must be a pointer to such an object, i.e. const char **).

    If PyArg_ParseTuple returns a falsy value, it means the conversion failed and a Python exception has been set. We raise the exception on Python side by returning NULL instead of Py_None from the function.

    Lastly,

    return Py_None; 
    

    is not correct - you must always increment the reference counter on any such value before returning it - that's what Py_RETURN_NONE macro does in it - it is functionally equivalent to

    Py_INCREF(Py_None);
    return Py_None;