Have you ever wondered how Python libraries are so fast? Well, it’s because they aren’t written in Python! C and C++ compose the major backbone that make Python so powerful. This week, we will look at how I adopted this idea for the spelling maze generator we built previously!
The week before last week’s post really exemplified why being able to program in C++ gives you unending power in the realm of efficiency gains. Unfortunately, however, most of the world has decided to move on to the cleaner and more abstract sandboxes of Python, mainly to reduce the development cost of building new software. I guess some people just aren’t here for the pain like others…
Anyways, this week’s post tackles how the hardcore C++ programmer can still make friends in the Python playground.
It’s Already Built
Lucky for us, people share their tunnels to brighter levels of existence — all we have to do is look and follow!
The simplest way to make your C++ program usable in the high-level language of Python is to use the Pybind11 Library.
What does it mean?
We just have to include a header file to be able to bind functions to a module that is importable/callable from a Python script.
Where would this be useful?
I am glad you asked! We already have a project that is a perfect candidate. Do you remember when we produced spelling mazes to help my son study for spelling? Then, we rewrote it in C++ to measure the efficiency gains…which were sizeable.
Well, why not pair the two? Then we can keep our web front-end built in Python and back it up with our C++ library!
Code Time (C++, CMake)
So really, our code is going to be very minimal. All we need to do is:
- Create a single function that accepts a word and saves our maze to a file
- Bind that function to a Python module using Pybind11’s PYBIND11_MODULE macro
The Function (C++)
void generate_maze(std::string word, int grid_width, int grid_height, std::string file_prefix, int block_width = 20, int block_height = 20){
generator.seed(time(0));
WordMaze m(word, grid_width, grid_height, block_width, block_height);
m.save_to_png(file_prefix + word + ".png");
}
Above, you can see the function we use to create our WordMaze object in C++ and save it off to a file. You might notice we are taking in a few more parameters than we had previously discussed. This is just to make it easier to configure the maze from the Python side.
The Module Binding (C++)
PYBIND11_MODULE(SpellingMaze, m) {
m.def("generate_maze", &generate_maze, "A function to generate a maze.");
}
Could it really be that simple? Well, yes and no. This is the code that is required, but actually building it is the part that will sneak up on you (especially if you are on a Windows machine).
Building
To preface this section, I will say:
If you are building a first-party app without third party dependencies in C++, building with Pybind11 is going to be really easy (their website does an excellent job of walking you through the many ways to build).
Travis (He’s a NOOB at C++ build systems)
However, we don’t have it that easy, since we need to also link against SFML.
Story Time
So, with my little experience setting up C++ build systems (none), I set out to combine the powers of SFML and Pybind11 to create a super fast high-level Maze Generator!
SFML, Pybind11 and MSYS2 walk into a bar…
Previously, I wrote an article on how to set up a Windows build environment for compiling simple SFML applications. Looking back now, I realize there are definitely better ways to do this.
For this project, I struggled to wrap my head around including Pybind11 in my already convoluted SFML build system. From the examples given on the Pybind11 website, it was clear my CMake approach wasn’t their go-to option for building.
In fact, the CMake example they give requires that the entire Pybind11 repository be included as a submodule to my repository. So, after a few failed attempts at copy-pasting their CMake config, I took a different approach.
Side note: I like using Visual Studio Code. It is by far my favorite IDE with all of its plugins and remote development solutions. However, in this case, the CMake plugin makes things nearly impossible to understand which configuration is being generated.
Eventually, I opted for using MSYS2 directly. I was able to install the Pybind11 and SFML dependencies using the following commands:
pacman -S mingw-w64-x86_64-pybind11
pacman -S mingw-w64-x86_64-sfml
This made finding them as easy as including these lines in my CMakeLists.txt:
find_package(SFML 2.5 COMPONENTS system window graphics network audio REQUIRED)
find_package(pybind11 REQUIRED)
Here is our final CMakeLists.txt file:
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
project(SpellingMaze VERSION 0.1)
find_package(SFML 2.5 COMPONENTS system window graphics network audio REQUIRED)
find_package(pybind11 REQUIRED)
pybind11_add_module(SpellingMaze src/spelling_maze.cpp)
target_link_libraries(SpellingMaze PRIVATE sfml-graphics)
Conclusion
Just like that, we can bring the power of C++ to the world of Python (even on a Windows machine!). I hope you enjoyed this post! I am always open to feedback. Let me know if there is anything you would like me to dive into, attempt, and/or explain! I am more than willing to do so. Please add your questions and suggestions in the comments below!
Thanks!
-Travis