Calling Python code from a C++ application


Calling Python from C++

Have you ever been writing C++ and you find yourself writing non-performance-critical code that would be absolutely trivial in Python? Or perhaps there is some complex problem that has an optimized solution in a python package, and if you could just call it from your C++ code life would be so much better. It turns out that this is fairly easy to do. I’ve talked about using Cython to wrap C/C++ for python, and as it happens, Cython can this job in reverse as well. To demonstrate this, I’ll call into the art package from a simple C++ program. First, we need the art package installed:

pip install art

Now lets write a Cython file that exposes a function that accepts a std::string and calls the text2art function in the art package and returns the result.

# art_wrapper.pyx

from libcpp.string cimport string
from art import text2art as _text2art

cdef public string text2art(string text):
    return _text2art(text)
If you don’t have Cython installed, you will also need to:

pip install cython
Because we used the public keyword in our cdef function, when we call:

cython -2 art_wrapper.pyx

Two files are generated: art_wrapper.c and art_wrapper.h. We include this generated header file in our C++ program:

// main.cpp

#include  <iostream>
#include  "Python.h"
#include  "art_wrapper.h"

int  main(int argc, char  *argv[])
    std::cout << text2art("Python in C++!") << std::endl;
    return 0;

Now we are ready to build!

g++ art_wrapper.c main.cpp -o main $(python-config --libs)  $(python-config --includes)  $(python-config --cflags)

Lets run it!

$ ./main
               _    _                     _           ____                _ 
 _ __   _   _ | |_ | |__    ___   _ __   (_) _ __    / ___|   _      _   | |
| '_ \ | | | || __|| '_ \  / _ \ | '_ \  | || '_ \  | |     _| |_  _| |_ | |
| |_) || |_| || |_ | | | || (_) || | | | | || | | | | |___ |_   _||_   _||_|
| .__/  \__, | \__||_| |_| \___/ |_| |_| |_||_| |_|  \____|  |_|    |_|  (_)
|_|     |___/                                                               

A few notes:
  • I hope this is obvious, but including Python.h and running Py_Initialize embeds the python interpreter in your C++ application. Python is pretty light-weight, so this is generally not a problem.
  • This executable depends on python being installed on the system. In particular, the python shared libraries will need to be installed in a location where the loader can map them into the process at load-time. This is not a problem if python is installed on the target system in the usual way.
  • The call to initart_wrapper is Python2 specific. If you are using Python3, you would do: PyImport_AppendInittab("art_wrapper", PyInit_art_wrapper); prior to the Py_Initialize call. (the art_wrapper part of the function name is the cython module name, substitute your module name)

    As Zoltan Beck kindly points out in the comments, this is incomplete. To make this work in python3, the main file must be modified:
    PyImport_AppendInittab("art_wrapper", PyInit_art_wrapper);
    Also, the .pyx file needs to be updated:
    return _text2art(text.decode('ascii')).encode('ascii')
    I also needed to change the calls to python-config in the compilation command to python3.7-config and I needed to add -fpic
    End UPDATE

  • In the Cython code, all the public functions should return a C++ type and all the parameters should be C++ types. If you do not annotate the types, the type will default to PyObject* which introduces unnecessary complexity in your C++ code, especially if you are not already comfortable with the Python C-API.
  • The python-config program is awesome. Use it to generate the libs/includes/compiler flags.

2 thoughts on “Calling Python code from a C++ application

  1. You need much more to make this example work in python3 than what you put in the third note.
    1) you need a call both before and after the Py_Initialize call, otherwise you get a segmentation fault:
    PyImport_AppendInittab(“art_wrapper”, PyInit_art_wrapper);
    2) you also need to mess around with the unicode string types in your pyx file:
    cdef public string text2art(string text):
    utext = text.decode(‘UTF-8’)
    cdef string result = _text2art(utext).encode(‘UTF-8’)
    return result

Leave a Reply
