Awasu » Extending Python: Calling functions
Monday 4th November 2019 4:28 PM []

The most common reason for writing an extension module is because you have some code that needs to be written in C/C++[1]Perhaps for performance reasons, or you need to work with legacy code. that you want to call from Python, so we'll extend our minimal example with such a function.

But before we do this, we'll first change our module code from C to C++[2]Because, why not? :-) , which is very easy to do. We simply change the source file to have a .cpp extension, and add a language="c++" parameter to the setup script:

extn = Extension( "demo", sources=["init.cpp"], language="c++" )
setup(
    name = "demo",
    version = "0.1",
    description = "Demonstration of how to write a Python extension.",
    ext_modules = [ extn ]
)

I also added a version number and module description to the setup configuration.

Defining a new function

To add a function that can be called from Python, we need to update our module definition to point to a table of available functions:

static struct PyModuleDef gModuleDef = {
    PyModuleDef_HEAD_INIT,
    "demo",
    "Demonstration of how to write a Python extension.",
    -1,
    gMethodDefs // this points to a table of available functions
} ;

We then define our function table like this:

static PyMethodDef gMethodDefs[] = {
    { "add", py_add, METH_VARARGS, "Add two numbers." },
    { NULL, NULL, 0, NULL }
};

Each entry in this table specifies:

  • the Python name of the function
  • the name of the function as defined in our C++ code
  • how the function will receive its parameters
  • the function's docstring

Implementing the new function

Finally, we implement the function itself:

PyObject*
py_add( PyObject* pSelf, PyObject* pArgs )
{
    // extract the arguments
    int val1, val2 ;
    if ( ! PyArg_ParseTuple( pArgs, "ii", &val1, &val2 ) )
        return NULL ;

    // add the numbers
    int result = val1 + val2 ;

    return PyLong_FromLong( result ) ;
}

Because we declared this function as METH_VARARGS, we will receive a tuple of positional parameters passed in to us[3]We'll see in a later tutorial how to accept keyword parameters..

To keep things simple, our example will simply accept two numbers, and return their sum. We call PyArg_ParseTuple() to parse the parameters tuple, with a format specifier of "ii" to indicate we are expecting two integers. Note that if this fails e.g. because the caller passed in None, or a string, PyArg_ParseTuple() will fail and we return NULL to the caller, which is the convention to indicate that something went wrong[4]PyArg_ParseTuple() will have already set an appropriate error message, so that the caller will have some of idea of what exactly went wrong. We will look at how all this works in more detail in the next section..

Once we have the two numbers, we add them together and return the result as a Python object.

Note that Python functions (which this effectively is) always return a value, even if it's just None. Since returning NULL indicates an error, if the function has no return value, it must explicitly return None:

    Py_INCREF(Py_None) ;
    return Py_None ;

Or, you can just use the Py_RETURN_NONE macro, which does the same thing.

Calling the function

Build and install the extension as before:

python setup.py build install

We can now call our new add() function directly from Python:

  

Download the source code here.


« A minimal setup

Tutorial index

Handling errors »

   [ + ]

1. Perhaps for performance reasons, or you need to work with legacy code.
2. Because, why not? :-)
3. We'll see in a later tutorial how to accept keyword parameters.
4. PyArg_ParseTuple() will have already set an appropriate error message, so that the caller will have some of idea of what exactly went wrong. We will look at how all this works in more detail in the next section.
Have your say