Python meets C/C++, Teil 2: SWIG und pybind11

Seite 2: C++11 und Python mit pybind11 nahtlos verbinden

Inhaltsverzeichnis

Der Name ist Programm, denn pybind11 unterstützt eine nahtlose Zusammenarbeit (Seamless Operability) zwischen C++11 und Python. Nahtlos heißt in dem konkreten Fall, dass sich sowohl C als auch C++ um Python erweitern und andererseits C/C++ in Python einbinden lassen. Dafür ist es nicht unbedingt notwendig, sich mit dem sogenannten "Name Mangling" zu beschäftigen, also der etwas lästigen Eigenheit des C++-Compilers, klare Funktionsnamen wie "hello World" kryptisch umzuschreiben.

pybind11 ist vollständig in die Headerdateien implementiert. Damit steht die gesamte Funktionalität durch das Inkludieren von Headerdateien direkt zur Verfügung, ohne eine Bibliothek hinzulinken zu müssen. Es basiert auf Boost.Python – im Gegensatz zu Boost.Python besitzt pybind11 aber keine Abhängigkeiten zum Framework Boost, sondern stellt nur minimale Anforderungen an Python und den C++-Compiler.

Funktionen überladen und Name Mangling

Im Gegensatz zu Python unterstützt C++ das Überladen von Funktionen. Das heißt, dass sich Funktionen mit dem selben Namen, aber verschiedenen Parametern definieren lassen. Der Compiler sucht sich einfach die passenden Funktionen heraus, wenn er sie benötigt. Die entscheidende Frage ist: Wie kann der Compiler die verschiedenen Funktionen unterscheiden?

Der C++-Compiler encodiert dabei zusätzlich die Typen der Parameter in den Funktionsnamen, der Prozess nennt sich Name Mangling. Auf Deutsch bedeutet das Namensverstümmelung beziehungsweise Namensverfälschung. Der Prozess ist nicht standardisiert und jeder C++-Compiler kann ihn spezifisch umsetzen. Gelegentlich bezeichnen Entwicklerinnen und Entwickler das sogenannte Name Mangling auch als "Name Decoration“.

Weiterführende Informationen zum Name Mangling in C/C++ bietet der Blogartikel "Mixing C with C++", einen sprachunabhängigen Überblick zu der Methode enthält der Wikipedia-Artikel über Name Mangling.

Pybind11 benötigt einen Compiler für Python in den Versionen 2.7 oder 3.5 und höher. Darüber hinaus sind noch die Headerdateien der Python/C-API python-dev oder python3-dev erforderlich. Als C++-Compiler ist mindestens Clang 3.3, GCC 4.8 oder Microsoft Visual Studio 2015 (Update 3) notwendig. Sind diese Anforderungen erfüllt, stehen die Kernfeatures von C++11 zur Verfügung.

Die Vielseitigkeit von pybind11 ist beeindruckend. So lassen sich Lambda-Ausdrücke verwenden, Funktionen können ihre Argumente per Value, Referenz oder Zeiger erhalten und lassen sich überladen. Klassen können Methoden und Attribute besitzen, lassen sich mehrfach ableiten und erlauben Virtualität. Mit der Standard Template Library (STL) und weiteren Bibliotheken von C++11 lässt pybind praktisch keine Wünsche offen.

Besonders überraschend bei diesen Kernfeatures ist wohl, dass pybind11 das Überladen von Funktionen unterstützt. Python-Kennerinnen und -Kenner wissen, dass Python selbst das nicht tut, da es Attribute wie den Namen einer Funktion intern als Schlüssel in einem Dictionary speichert. Diese Schlüssel müssen eindeutig sein.

In bekannter Manier lässt sich die Funktionalität von pybind11 am einfachsten an einem Beispiel aufzeigen. Das folgende Listing stellt eine C++-Funktion vor, für die pybind11 eine Python-Bindung erzeugt.

#include <pybind11/pybind11.h>

int add(int i, int j) {
    return i + j;
}

PYBIND11_MODULE(function, m) {
    m.def("add", &add, "A function which adds two numbers");
}

Die C++/Python-Bindung besteht aus den folgenden Komponenten

  • #include <pybind11/pybind11.h> erzeugt die C++11/Python-Bindung
  • PYBIND11_MODULE wird durch die import-Anweisung aufgerufen
  • function: Name des Erweiterungmoduls, durch das sich das Modul in Python importieren lässt
  • m: Variable vom Typ py::module_, die als Interface für die Bindung verwendet wird
  • m.def macht Python mit der Funktion bekannt und bindet sich an das Erweiterungsmodul function. Dabei ist das erste Argument der Name der Python-Funktion, das zweite die Adresse der C-Funktion und das letzte Argument der Dokumentationsstring der Python-Funktion.

Das war der erste Streich, und das Bauen des Programms unter Linux ist mit python3-config dann fast schon ein Kinderspiel. Zunächst gilt es, die Headerdateien zu ermitteln python3 -m pybind11 -–includes , dann sind mit folgendem Befehl die Suffixe zu bestimmen: python3-config --extension-suffix . Daraus resultieren Kommandozeilenbefehle für das Bauen des Erweiterungsmoduls, und das Ergebnis sollte wie folgt aussehen: c++ -O3 –Wall –std=c++11 -shared -fPIC $(python3 -m pybind11 --includes) function.cpp -o function$(python3-config --extension-suffix). Mit den vorgestellten Kommandozeilenbefehlen lässt sich das Modul function erstellen, importieren und verwenden (s. Abb. 5).

Verwenden des Moduls function (Abb. 5)

Das Erstellen des Erweiterungsmoduls unter Windows ist etwas anspruchsvoller, denn das Werkzeug python3-config ist dort nicht vorhanden. Mit einem kleinen Umweg über cmake lässt sich das Erweiterungsmodul auch für Windows bauen. Dazu sind die folgenden zusätzlichen Schritte notwendig: CMake ist zunächst zu installieren. Dann gilt es, die Umgebungsvariable pybind11_DIR auf das Verzeichnis zu setzen, in dem sich die pybind11-Konfigurationsdateien befinden, und zwar entweder pybind11Config.cmake oder pybind11-config.cmake. Die CMake-Konfigurationsdateien sind Bestandteil von pybind11. Abschließend ist noch die CMake-Konfigurationsdatei CmakeLists.txt wie das folgende Listing zu erzeugen.

cmake_minimum_required (VERSION 2.6)

project (pybind)
enable_language(C)
enable_language(CXX)

find_package(pybind11 CONFIG REQUIRED)
include_directories(${pybind11_INCLUDE_DIRS})
message([MAIN] "Found pybind11 v${pybind11_VERSION}: ${pybind11_INCLUDE_DIRS}")

MESSAGE( [Main] " pybind11_INCLUDE_DIRS = ${pybind11_INCLUDE_DIRS}")
MESSAGE( [Main] " pybind11_LIBRARIES = ${pybind11_LIBRARIES}")

pybind11_add_module(examplelib example.cpp)

Damit CMake eine pybind11-Konfigurationsdatei in Zeile 7 findet, ist das Setzen der Umgebungsvariablen pybind11_DIR notwendig. Die letzte Zeile pybind11_add_module(examplelib example.cpp) erzeugt das Modul function, das aus der Datei example.cpp besteht.

Soll das Erweiterungsmodul sowohl unter Linux als auch unter Windows erzeugt werden, bietet es sich an, auf beiden Plattformen CMake einzusetzen.

Zugegebenermaßen ist das hier gezeigte Erweiterungsmodul ein einfaches Beispiel, denn es verwendet noch keine C++-Features. Die nächste, etwas komplexere Stufe stellt das im folgenden Listing veranschaulichte neue Erweiterungsmodul function dar.

#include <pybind11/pybind11.h>

namespace py  = pybind11;

int add(int i, int j) {
    return i + j;
}

int sum(int i, int j) { return add(i, j); }
int sum(int i, int j, int k) { return i + j + k;}

PYBIND11_MODULE(function, m) {
    m.def("add", &add, "A function which adds two numbers",
        py::arg("i") = 2000, py::arg("j") = 11);
    m.def("sum", py::overload_cast<int, int>(&sum), "Sum up two values");
    m.def("sum", py::overload_cast<int, int, int>(&sum), "Sum up three values");

    m.attr("year") = 2011;
    m.attr("language") = "C++11";
}

Um die Funktionalität von pybind11 anzusprechen, kann der Alias namespace py = pybind11 (Zeile 3 in Listing 7) zum Einsatz kommen. Die Funktion add in Zeile 13 und 14 ist ausgesprochen mächtig: Zum einen unterstützt sie Schlüsselwortargumente und zum anderen setzt sie diese auf Defaultwerte. Der Name des ersten Funktionsparameters lautet i und sein Defaultwert ist 2000. Interessant sind auch die überladenen Funktionen sum in Zeile 9 und 10, die zwei beziehungsweise drei Argumente benötigen. Mit der Funktion py::overload_cast<int, int>(&sum) ist es möglich, die Funktion sum in Python mit zwei beziehungsweise drei Argumenten aufzurufen. Zum Abschluss stellt das Modul noch die Attribute year und language zur Verfügung. Abbildung 6 zeigt das Modul in der Anwendung.

Verwenden des erweiterten Modules [code]function[/code] (Abb. 6)

Nachdem das Modul function importiert ist, lässt es sich mit der Python-Funktion dir genauer analysieren. Die Funktion add lässt sich mit zwei Argumenten, ohne Argument oder mit benannten Argumenten aufrufen. Da die C++-Funktion zwei Ganzzahlen erwartet, führt der Aufruf mit den zwei Strings hello und world zur Fehlermeldung. Ruft man die Funktion sum mit zwei beziehungsweise drei Argumenten in Python auf, wählt der Python-Compiler die passende Überladung aus. Die Attribute year und language lassen sich dabei ausgeben und modifizieren.

Der Aufruf von objektorientiertem C++-Code geht mit pybind11 ähnlich leicht von der Hand wie der Umgang mit Funktionen. Folgende Codepassage stellt die einfache Klasse HumanBeing vor.

#include <pybind11/pybind11.h>
#include <string>

struct HumanBeing {
    HumanBeing(const std::string& n) : name(n) { }
    const std::string& getName() const { return name; }
    std::string name;
    std::string familyName{"Grimm"};
    virtual ~HumanBeing() = default;
};

struct Woman: HumanBeing {
    Woman(const std::string& name): HumanBeing(name) { }
    std::string gender() const { return "female"; }
};

namespace py = pybind11;

PYBIND11_MODULE(human, m) {
    py::class_<HumanBeing>(m, "HumanBeing")
        .def(py::init<const std::string &>())
        .def("getName", &HumanBeing::getName)
        .def("__repr__", [](const HumanBeing& h) { return "HumanBeing: " + h.name; })
        .def_readwrite("familyName", &HumanBeing::familyName);

    py::class_<Woman, HumanBeing>(m, "Woman")
        .def(py::init<const std::string &>())
        .def("gender", &Woman::gender);      
}

Der C++-Code enthält die Klasse HumanBeing und die von ihr abgeleitete Klasse Woman. Durch py::class werden in den Zeilen 20 und 26 jeweils die Wrapper für Python erzeugt. Der Wrapper-Code enthält einige Python-Besonderheiten. Zum einen definiert def(py::init<const std::string &>()) (Zeile 21) den Konstruktor der Klasse für den Python-Code, zum anderen definiert der Aufruf .def("__repr__", [](const HumanBeing& h) { return "HumanBeing: " + h.name; }) die Python-Funktion __repr__, die den Ausgabeoperator für Instanzen der Klasse HumanBeing darstellt. Bei der Ausgabe wird der C++-Lambda-Ausdruck [](const HumanBeing& h) { return "HumanBeing: " + h.name; } ausgeführt. Durch .def_readwrite("familyName", &HumanBeing::familyName) in Zeile 24 besitzt die Klasse somit ein les- und schreibbares Attribut.

Abbildung 7 zeigt den Umgang mit der Klasse Woman.

Verwenden des Erweiterungsmodules human (Abb. 7)

Nachdem dieser und der vorherige Artikel (Python meets C/C++, Teil 1: Python um C/C++ erweitern oder darin einbetten) verschiedene Wege mit dem Modul ctypes, nativ und mit den Frameworks SWIG und pybind11 vorgestellt haben, um Python um C beziehungsweise C++ zu erweitern, gilt es nun, die Strategie auf den Kopf zu stellen. Im Folgenden stellt der Artikel das Einbetten von Python in C und C++ vor.