Use pybind11 for a detailed but simple example


Pybind11 can be used to import C++ libraries into Python. Here I create a C++ project, the robot, to resemble real-life software as much as possible. Then, I write a piece of code to compile the project as a Python module. The key point is to only expose end-user (API) classes and functions.


A detailed C++ project, the robot, is created, then its API is exposed to Python.

In the example we see:

  • C++ classes and their components are re-defined as python classes:
    • constructors
    • methods
    • public data members
    • setter and getter (property)
  • A class template is also exposed.
  • Polymorphic objects are injected into constructors.
  • C++ functions are converted to Python functions
  • Polymorphic objects are passed to functions in Python
  • STL containers data are received in Python

Final Code

The final code is on GitHub. It is explained in the following sections.

Robot Code

The code creates robots and asks them to do tasks.

An interface for the robot is:

class IRobot
  std::string name;
  ISpeech &speech;

  IRobot(std::string name_, ISpeech &speech_) : 
    name(name_), speech(speech_){};

  virtual void Walk() = 0;
  void Talk() { speech.Talk(); }

Where we have two different speech devices, V1 and V2:

class ISpeech
    std::string model;

    ISpeech(std::string model_) : model(model_){};

    virtual void Talk() = 0;

class SpeechV1 : public ISpeech
    SpeechV1(std::string model_) : ISpeech(model_){};
    void Talk() override { 
        std::cout << "Talking with Speech version 1.0. \n"; }

class SpeechV2 : public ISpeech
    SpeechV2(std::string model_) : ISpeech(model_){};
    void Talk() override { 
        std::cout << "Talking with Speech version 2.0. \n"; }

Robot T800 specs:

  • A polymorphic class made of IRobot
  • Has constructor accepts ISpeech objects
  • has Public member: year
  • Has virtual function: Walk()
  • Provides a complex STL container: GetData()
class T800 : public IRobot

    int year;
    T800(std::string name, int year_, ISpeech &speech_) : IRobot(name, speech_), year(year_){};
    virtual void Walk() override{std::cout << "T-800 is walking...\n";};
    auto GetData(){
        std::vector<std::tuple<std::string,double>> data;
        data.push_back(std::make_tuple("book", 1.5));
        data.push_back(std::make_tuple("table", 2.5));
        data.push_back(std::make_tuple("wall", 3.5));
        return data;

And Robot T1000 specs:

  • is a class template
  • is a polymorphic class made of IRobot
  • Has constructor accepts ISpeech objects
  • Has property Height
  • Has virtual function Walk
template <typename T>
class T1000 : public IRobot
    T height;

    T1000(std::string name_, T height_, ISpeech &speech_)
        : IRobot(name_, speech_), height(height_){};

    const auto &GetHeight() { return height; }
    auto SetHeight(T height_) { height = height_ ;}
    virtual void Walk() override { std::cout << "T-1000 is walking...\n"; };

And finally we have a helper function which accepts any robot:

void Move(IRobot& robot){
    std::cout<< <<"\n";

pybind11 binding

The classes and their members are declared in the following way:

PYBIND11_MODULE(moduleName, m)
  // define all classes
  py::class_<NameOfClass, ClassInterface>(m, "NameOfClass")
        .def(py::init<type1, type2, type3>()) // constructor
        .def("aFunction", &NameOfClass::aFunction)
        .def_readwrite("aPublicMember", &NameOfClass::aPublicMember);

  // define all standalone functions
  m.def("StandAloneFunction", &StandAloneFunction);
  • moduleName: the name of the module in Python
  • m : pybind11 handle, accept it as it is
  • NameOfClass: the name of the class to be exposed to Python
  • ClassInterface: the interface or parent of the class. It is not necessary, if polymorphism is not needed to be captured in Python.
  • type1, typ2,…: the type of arguments to the constructor of the class
  • aFunction: name of a function member of the class
  • aPublicMember: a public member of the class

We can also declare functions that don’t belong to any class:

PYBIND11_MODULE(moduleName, m)
  // define all classes
  // ...

  // define all standalone functions
  m.def("StandAloneFunction", &StandAloneFunction);

Robot to Python

Now we can declare robot code to Python. I create a folder pythonApi outside src folder and put binding code there. In this way, the main code is not polluted.

The binding code is:

#include "../src/t800.h"
#include "../src/t1000.h"
#include "../src/helper.h"

#include "../pybind11/include/pybind11/pybind11.h"
// to convert C++ STL containers to python list, see T800.GetData()
#include "../pybind11/include/pybind11/stl.h" 

namespace py = pybind11;
using namespace std;
PYBIND11_MODULE(robot, m)
    // Do not add abstract class constructor
    // We are just declaring it to python. Because
    // It is an argument type in T800, T1000  constructors
    // and also an argument type of Move() function.
    py::class_<ISpeech>(m, "ISpeech");

    // Add the base class to work polymorphism.
    // For example T800 constructed with ISpeech, if
    // we don't declare it here, python doesn't allow
    // injectign SpeechV1 to T800 constructor.
    py::class_<SpeechV1, ISpeech>(m, "SpeechV1")
        .def(py::init<string>()); //Constructor

    py::class_<SpeechV2, ISpeech>(m, "SpeechV2")
        .def(py::init<string>()); // Constructor

    py::class_<IRobot>(m, "IRobot"); // Abstract, do not add constructor
    py::class_<T800, IRobot>(m, "T800")
        .def(py::init<string, int, ISpeech &>()) // constructor
        .def("Walk", &T800::Walk)
        .def("Talk", &T800::Talk)
        .def("GetData", &T800::GetData)
        // read-write public data memeber
        // you can use def_readonly as well.
        .def_readwrite("year", &T800::year); 

    using T = double;
    py::class_<T1000<T>, IRobot>(m, "T1000")
        .def(py::init<string, T, ISpeech &>()) // constructor
        .def("Walk", &T1000<T>::Walk) // method
        .def("Talk", &T1000<T>::Talk) // method
        // Define property with getter and setter
        .def_property("height", &T1000<T>::GetHeight,&T1000<T>::SetHeight); 

    m.def("Move", &Move);


Clone pybind11

In the project folder, clone pybind11

git clone

Look at .gitignore file, pybind11 repo is ignored as we just read it.


The CMake Code is as simple as:

cmake_minimum_required(VERSION 3.1.0)


pybind11_add_module(robot "./pythonApi/robot.cpp")

Note that name robot is consistent with the module name we use in bindings

PYBIND11_MODULE(robot, m) {/* code */}


Open a terminal, in the project folder, run

mkdir build
cd build
cmake ..

In the build directory, you should have a compiled module with the name similar to:


In a terminal being in the build directory, run


in Python, import the library

import robot 

and work with the classes and functions imported.


There is a Python example in pythonApi/ to use robot module:

from robot import *

s1 = SpeechV1("model_V1_ab")
s2 = SpeechV2("model_V2_yz")

# Create robots with different Speech systems
t800 = T800("Jack", 2020, s1)
t1000 = T1000("Kate", 0, s2)

# returns STL container in python list


# set, get property

# Moves any robot
Tags ➡ C++ Python


I notify you of my new posts

Latest Posts


1 comment
soundar 29-Jun-2022
That's a nice work. Well explained. Any idea how to create wrapper for C/CPP structure, pointers and enums? Have you tried anything?