Posts
Search
Contact
Cookies
About
RSS

Create a Python module with the C API

Added 12 Nov 2022, 10:43 a.m. edited 18 Jun 2023, 1:12 a.m.

While you would normally create the C portions of a Python module in a separate library, there are however some instances where a separate library might not be desirable.

In this example, a script will be run which can use functions that are part of the embedding application, lets take a look at the python script

# test.py

import funcMod

def main(text, i, j):
    
    print ('Passed to python "'+text+'" : '+str(i)+' : '+str(j) )

    print ('mod = '+str(funcMod.mod(i,j)))

    r = funcMod.func(i,j)

    return r

The module "funcMod" doesn't exist in the traditional sense, there are no files for Python to load when this module is imported. This module contains two functions "mod" and "func", that provide trivial functionality for the sake of example.

Before the Python module can be added to the interpreters state, it must be initialised so that Python knows not just what functions it has but also where the implementations are.

/* Module method table */
static PyMethodDef funcMethods[] =
{
    {"func", func, METH_VARARGS, "Add two ints"},
    {"mod", funcModulus, METH_VARARGS, "modulus"},
    { NULL, NULL, 0, NULL}
};

/* Module structure */
static struct PyModuleDef funcmodule =
{
    PyModuleDef_HEAD_INIT,
    "funcMod", /* name of module */
    "function module", /* Doc string (may be NULL) */
    -1, /* Size of per-interpreter state or -1 */
    funcMethods /* Method table */
};

The first structure details the function names and the functions they correspond to, as well as the function signature and document string. The second structure is more overall data regarding the module as a whole. The name of the module and a reference to the method table.

    // create the function module
    PyObject* funcMod = PyModule_Create(&funcmodule);
    
    // add the module to the system modules so it can be imported
    PyObject* sys_modules = PyImport_GetModuleDict();
    PyDict_SetItemString(sys_modules, "funcMod", funcMod);
    Py_DECREF(funcMod);

While actually creating the module is trivial, there is one minor wrinkle in that we need to "insert" the module into the interpreters list of built in modules.

The rest of the code (below) is entirely concerned with running a Python script and follows the normal pattern you'd expect when embedding Python in an application. While there are other reasons for creating a Python module "on the fly" one of the most useful features of this technique is that assuming you make your application self contained (statically linked) then it is entirely self contained, no custom module needs installing...

Enjoy!

// gcc -g $(python3-config --cflags --ldflags --embed) test.c -o test

#include <Python.h>
#include <stdlib.h>

static PyObject* func(PyObject * self, PyObject * args)
{
    int a, b, result;
    if (! PyArg_ParseTuple(args, "ii", &a, &b))
    {
        return NULL;
    }
    result = a + b;
    return Py_BuildValue("i", result);
}


static PyObject* funcModulus(PyObject * self, PyObject * args)
{
    int a, b, result;
    if (! PyArg_ParseTuple(args, "ii", &a, &b))
    {
        return NULL;
    }
    result = a % b;
    return Py_BuildValue("i", result);
}


/* Module method table */
static PyMethodDef funcMethods[] =
{
    {"func", func, METH_VARARGS, "Add two ints"},
    {"mod", funcModulus, METH_VARARGS, "modulus"},
    { NULL, NULL, 0, NULL}
};

/* Module structure */
static struct PyModuleDef funcmodule =
{
    PyModuleDef_HEAD_INIT,
    "funcMod", /* name of module */
    "function module", /* Doc string (may be NULL) */
    -1, /* Size of per-interpreter state or -1 */
    funcMethods /* Method table */
};

int main()
{
    // Set PYTHONPATH to the working directory
    setenv("PYTHONPATH",".",1);

    // Initialize the Python Interpreter
    Py_Initialize();

    // create the function module
    PyObject* funcMod = PyModule_Create(&funcmodule);
    
    // add the module to the system modules so it can be imported
    PyObject* sys_modules = PyImport_GetModuleDict();
    PyDict_SetItemString(sys_modules, "funcMod", funcMod);
    Py_DECREF(funcMod);

    // the name of the script to run
    PyObject* scriptName = PyUnicode_FromString((char*)"test");

    // import the script
    PyObject* scriptModule = PyImport_Import(scriptName);
    if (!scriptModule) {
        PyErr_Print();
        return -1;
    }

    // pDict is a borrowed reference, get all its functions
    PyObject* pDict = PyModule_GetDict(scriptModule);

    // pFunc is also a borrowed reference, the function to call 
    PyObject* pFunc = PyDict_GetItemString(pDict, (char*)"main");


    PyObject *pValue = NULL;   // for the parameters
    PyObject *pResult = NULL;  // value returned from the script
    
    if (PyCallable_Check(pFunc))
    {
        // build the parameters
        pValue=Py_BuildValue("(z,i,i)",(char*)"Test string",(int*)37, (int*)5);
        PyErr_Print();
        // finally call it.
        pResult=PyObject_CallObject(pFunc, pValue);
        PyErr_Print();
    } else {
        PyErr_Print();
    }
    
    // return value from the script
    printf("Result is %ld\n",PyLong_AsLong(pResult));
    Py_DECREF(pValue);

    // Clean up
    Py_DECREF(scriptModule);
    Py_DECREF(scriptName);

    // Finish the Python Interpreter
    Py_Finalize();

    return 0;
}