Close

Calling Python code from a C++ application

python-from-c++

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[])
{
    Py_Initialize();
    initart_wrapper();
    std::cout << text2art("Python in C++!") << std::endl;
    Py_Finalize();
    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)

    UPDATE:
    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);
    Py_Initialize();
    PyImport_ImportModule("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.

4 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);
    Py_Initialize();
    PyImport_ImportModule(“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

    1. David, assuming you are on linux, it will be in /usr/bin
      There should be a version of it for each python version you have installed. For example, the command

      ls -l /usr/bin/python*-config
      

      yields the following on my system:

      lrwxrwxrwx 1 root root 33 Mar  8 05:02 /usr/bin/python2.7-config -> x86_64-linux-gnu-python2.7-config
      lrwxrwxrwx 1 root root 16 Mar 13  2020 /usr/bin/python2-config -> python2.7-config
      lrwxrwxrwx 1 root root 34 Aug  7 09:36 /usr/bin/python3.10-config -> x86_64-linux-gnu-python3.10-config
      lrwxrwxrwx 1 root root 38 Aug  7 09:36 /usr/bin/python3.10-dbg-config -> x86_64-linux-gnu-python3.10-dbg-config
      lrwxrwxrwx 1 root root 35 Aug  7 09:36 /usr/bin/python3.10d-config -> x86_64-linux-gnu-python3.10d-config
      lrwxrwxrwx 1 root root 33 Jul  3 10:58 /usr/bin/python3.7-config -> x86_64-linux-gnu-python3.7-config
      lrwxrwxrwx 1 root root 37 Jul  3 10:58 /usr/bin/python3.7-dbg-config -> x86_64-linux-gnu-python3.7-dbg-config
      lrwxrwxrwx 1 root root 35 Jul  3 10:58 /usr/bin/python3.7dm-config -> x86_64-linux-gnu-python3.7dm-config
      lrwxrwxrwx 1 root root 34 Jul  3 10:58 /usr/bin/python3.7m-config -> x86_64-linux-gnu-python3.7m-config
      lrwxrwxrwx 1 root root 33 Jun  2 03:49 /usr/bin/python3.8-config -> x86_64-linux-gnu-python3.8-config
      lrwxrwxrwx 1 root root 33 Jul  3 09:40 /usr/bin/python3.9-config -> x86_64-linux-gnu-python3.9-config
      lrwxrwxrwx 1 root root 16 Mar 13  2020 /usr/bin/python3-config -> python3.8-config
      lrwxrwxrwx 1 root root 14 Apr 15  2020 /usr/bin/python-config -> python2-config

Leave a Reply to David Klein Cancel reply

Your email address will not be published. Required fields are marked *