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 theart
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 runningPy_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 thePy_Initialize
call. (theart_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:
Also, the .pyx file needs to be updated:PyImport_AppendInittab("art_wrapper", PyInit_art_wrapper); Py_Initialize(); PyImport_ImportModule("art_wrapper");
I also needed to change the calls toreturn _text2art(text.decode('ascii')).encode('ascii')
python-config
in the compilation command topython3.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 toPyObject*
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.
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
Thanks Zoltan, you are absolutely correct. I’ll update the post accordingly.
Hi, where can I find python-config?
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
yields the following on my system: