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

Seite 3: Python in C und C++ einbetten

Inhaltsverzeichnis

Die Anforderung, Python in C oder C++ einzubetten sind dieselben, die es bei der Erweiterung von Python um C/C++ zu meistern galt. Lediglich die Reihenfolge ist beim Einbetten auf den Kopf gestellt. Zunächst sind die Werte von C/C++ nach Python zu konvertieren. Anschließend gilt es, die konvertierten Werte zum Ausführen der Python-Funktionalität zu verwenden. Zuletzt lassen sich die Ergebnisse dann von Python nach C beziehungsweise C++ konvertieren.

Prinzipiell bieten sich zwei Möglichkeiten an, um Python-Code in C/C++ auszuführen. Zum einen lässt sich der Python-Code als String oder als Modul verpacken und vollständig ausführen. Darüber hinaus ist es möglich, explizit Code aus einem Python-Modul auszuführen. Diese zweite Variante ist deutlich anspruchsvoller.

Die größte Hürde beim Einbetten besteht darin, auf Linux und Windows die richtigen Compiler- und Linkeroptionen zu bestimmen. Daher ist ein kleiner Umweg notwendig.

Mit dem Executable python-config lassen sich die Optionen für den Compiler (python-config --clflags) sowie für den Linker (python-config --ldflags) auf Linux bestimmen. Exemplarisch stellen Abbildungen 8 und 9 sowie die GCC-Kommandozeilenbefehle im folgenden Listing das Erzeugen des Executables für Python 3.8 auf der Linux-Plattform des Autors vor.

python3.8-config --cflags (Abb. 8)

python3.8-config --ldflags (Abb. 9)
g++ $(python3.8-config --cflags)   showTimeString.cpp  $(python3.8-config --ldflags) -o showTimeString

Unter Windows sollten die Kommandozeilenbefehle zum Erzeugen des Executables die Struktur wie im nächsten Listing besitzen. Wie im vorigen Abschnitt kommt auch in diesem Fall Python 3.8 zum Einsatz.

cl.exe /I<Path to Python.h> showTimeString.cpp /link /LIBPATH:<Path to python libraries>

Abbildung 10 zeigt das erfolgreiche Erzeugen und Ausführen des Executables unter Windows.

Erzeugen und Ausführen des Executables auf Windows (Abb. 10)

Nach diesen Trockenübungen lassen sich alle folgenden C-Programme, die Python-Funktionalität einbetten, erzeugen und ausführen. Das direkte Ausführen eines Python-Strings oder Python-Moduls sollte nun leicht von der Hand gehen.

Das einfache C-Programm showTimeString.cpp in Listing 11 dient als erstes Beispiel.

#include <Python.h>

int main(int argc, char* argv[]) {

    Py_Initialize();
    PyRun_SimpleString("import time\n"
                        "print(time.ctime(time.time()))");
    Py_Finalize();

}

Mit der Headerdatei <Python.h> stehen die Anweisungen in dem main-Programm zu Verfügung. Py_Initialize initialisiert den Python-Interpreter. PyRun_SimpleString führt den Python-String direkt aus, der zuerst das Modul time lädt, um anschließend die aktuelle Uhrzeit auszugeben. Py_Finalize fährt den Interpreter wieder runter. Die Ausgabe des Programm lässt sich in Abbildung 8 nachvollziehen.

Ähnlich einfach ist das Ausführen eines Python-Moduls in dem folgenden C-Programm. Der Quellcode im folgenden Listing stellt das Python-Modul showTime.py vor.

import time

print(time.ctime(time.time()))

Ähnlich wie bei dem Python-String im vorherigen Listing importiert das Python-Modul auch das Modul time und stellt die aktuelle Uhrzeit dar. Deutlich komfortabler ist hingegen das C-Programm showTimeModule.cpp im folgenden, das das Python-Modul lädt und ausführt.

#include <Python.h>
#include <stdio.h>

int main(int argc, char* argv[]) {

    Py_Initialize();
    FILE* pyFile = fopen("showTime.py", "r");
    if (pyFile) {
        PyRun_SimpleFile(pyFile, "showTime.py");
        fclose(pyFile);
    }
    Py_Finalize();
}

Das C-Programm öffnet in diesem Fall die Datei showTime.py und führt das Python-Modul mithilfe des Befehls PyRun_SimpleFile direkt aus. Die Ausgabe des Programms entspricht der in Abbildung 11. Im folgenden Abschnitt wird es anspruchsvoller.

Ausgabe des Programms showTime (Abb. 11)

Das nächste Listing steht für ein kleines Python-Modul myMath, das Funktionen für das Berechnen der Fakultät, der Summe und Produkts von Zahlen anbietet.

#include <Python.h>
#include <stdio.h>

int main(int argc, char* argv[]) {

    Py_Initialize();
    FILE* pyFile = fopen("showTime.py", "r");
    if (pyFile) {
        PyRun_SimpleFile(pyFile, "showTime.py");
        fclose(pyFile);
    }
    Py_Finalize();
}

Mit dem Executable runPythonFunction ist es möglich, die Python-Funktionen direkt auszuführen. Abbildung 12 stellt alle drei Funktionen im Einsatz dar.

Direktes Ausführen ausgewählter Python-Funktionen (Abb. 12)

Um das Puzzle zusammenzufügen, fehlt noch das C-Programm <C>runPythonFunction.c<C>, das das folgende Listing vorstellt:

#define PY_SSIZE_T_CLEAN
#include <Python.h>

int main(int argc, char *argv[]) {
    PyObject *pName, *pModule, *pFunc;
    PyObject *pArgs, *pValue;
    int i;

    if (argc < 3) {                                                    // (1)
        fprintf(stderr,"Usage: runPythonFunction <Python Module> <Function name> [Arguments]\n");
        return 1;
    }

    // argv[1]: Module name
    // argv[2]: Function name
    // argv[3] - argv[n]: Function arguments

    Py_Initialize();
    // Add the local directory to sys.path                            // (2)
    PyObject *sysmodule = PyImport_ImportModule("sys");
    PyObject *syspath = PyObject_GetAttrString(sysmodule, "path");
    PyList_Append(syspath, PyUnicode_FromString("."));                  
    
    // Import module                                                  // (3)
    pName = PyUnicode_DecodeFSDefault(argv[1]);
    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if (pModule != NULL) {                                            
        // Get function name
        pFunc = PyObject_GetAttrString(pModule, argv[2]);
        if (pFunc && PyCallable_Check(pFunc)) {
            pArgs = PyTuple_New(argc - 3);                            // (4)
            // Parse function arguments
            for (i = 0; i < argc - 3; ++i) {
                pValue = PyLong_FromLong(atoi(argv[i + 3]));
                if (!pValue) {
                    Py_DECREF(pArgs);
                    Py_DECREF(pModule);
                    fprintf(stderr, "Cannot convert argument\n");
                    return 1;
                }
                PyTuple_SetItem(pArgs, i, pValue);
            }
            pValue = PyObject_CallObject(pFunc, pArgs);               // (5)
            Py_DECREF(pArgs);
            if (pValue != NULL) {                                     // (6)
                printf("C: Result of function call: %ld\n", PyLong_AsLong(pValue));
                Py_DECREF(pValue);
            }
            else {
                Py_DECREF(pFunc);
                Py_DECREF(pModule);
                PyErr_Print();
                fprintf(stderr,"Function call failed\n");
                return 1;
            }
        }
        else {
            if (PyErr_Occurred()) PyErr_Print();
            fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
        }
        Py_XDECREF(pFunc);
        Py_DECREF(pModule);
    }
    else {
        PyErr_Print();
        fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
        return 1;
    }
    if (Py_FinalizeEx() < 0) {
        return 1;
    }
    return 0;
}

Aus der Vogelperspektive betrachtet führt das C-Programm die folgenden Schritte durch:

  1. Lesen der Kommandozeile
  2. sys.path um das lokale Verzeichnis erweitern; sys.path ist der pythonspezifische Pfad für das Suchen für Module
  3. Importieren des Python-Moduls
  4. Parsen der Funktionsargumente
  5. Aufruf der Python-Funktion
  6. Das Ergebnis der Python-Funktion in C verwenden