37.3 Python als eingebettete Skriptsprache 

In den vorangegangenen Abschnitten haben Sie Möglichkeiten kennengelernt, in C geschriebene Programmteile von einem Python-Programm aus aufzurufen und so beispielsweise laufzeitkritische Teile in ein C-Programm auszulagern.
In diesem Abschnitt wird der entgegengesetzte Weg beschritten: Wir möchten Python-Programme aus einem C/C++-Programm heraus ausführen, Python also als eingebettete Skriptsprache (engl. embedded script language) verwenden. Auf diese Weise können wir bestimmte Teile eines C-Programms in Python schreiben, für die Python aufgrund seiner Flexibilität besser geeignet ist.
37.3.1 Ein einfaches Beispiel 

Zum Einstieg wird ein C-Programm erstellt, das ein möglichst simples Python-Programm ausführt. Dieses Python-Programm gibt lediglich ein wenig Text und eine Zufallszahl auf dem Bildschirm aus:
#include <Python.h>
const char *programm =
"import random\n"
"print('Guten Tag, die Zahl ist:', random.randint(0, 100))\n"
"print('Das war ... Python')\n";
int main(int argc, char *argv[])
{
Py_Initialize();
PyRun_SimpleString(programm);
Py_Finalize();
}
Zunächst wird die Header-Datei der Python API eingebunden. Sie sehen, dass sowohl zum Erweitern als auch zum Einbetten von Python dieselbe API verwendet wird. Danach wird der String programm angelegt, der den später auszuführenden Python-Code enthält.
In der Hauptfunktion main wird der Python-Interpreter zuerst durch Aufruf von Py_Initialize initialisiert. Danach wird das zuvor im String programm abgelegte Python-Skript durch Aufruf der Funktion PyRun_SimpleString[ 166 ](Anstelle der Funktion PyRun_SimpleString hätten wir auch die Funktion PyRun_SimpleFile aufrufen können, um den Python-Code aus einer Datei zu lesen. ) ausgeführt und der Interpreter schließlich durch die Funktion Py_Finalize wieder beendet.
Wichtig ist, dass dem Compiler das Verzeichnis bekannt ist, in dem die Header-Datei Python.h liegt. Außerdem muss das Programm gegen die Python API gelinkt werden.
Kompilieren unter Windows
Um das Programm unter Windows zu kompilieren, können Sie die Visual Studio Build Tools verwenden, was allerdings mit relativ viel Aufwand bei der Installation verbunden ist.
Alternativ installieren Sie mingw über conda:
conda install mingw
Anschließend müssen Sie einen neuen Anaconda Prompt öffnen, damit der Compiler gefunden wird. Danach können Sie das Programm folgendermaßen übersetzen:
gcc -I%USERPROFILE%\Anaconda3\include -L%USERPROFILE%\Anaconda3
-lpython36 beispiel.c -o beispiel.exe
Durch den Aufruf wird die Datei beispiel.c in das ausführbare Programm beispiel.exe übersetzt.
Kompilieren unter Linux und macOS
Wenn Sie Anaconda unter Linux oder macOS verwenden, können Sie das Programm folgendermaßen übersetzen, sofern Sie einen entsprechenden Compiler installiert haben:
$ export LIBRARY_PATH=$HOME/anaconda3/lib:$LIBRARY_PATH
$ gcc -I$HOME/anaconda3/include/python3.6m -lpython3.6m beispiel.c -o beispiel
Zum Ausführen müssen Sie noch die Umgebungsvariable PYTHONHOME setzen und dafür sorgen, dass die Python-Bibliothek geladen werden kann:
$ export PYTHONHOME=$HOME/anaconda3/
$ export LD_LIBRARY_PATH=$HOME/anaconda3/lib:$LD_LIBRARY_PATH
Nun können Sie die ausführbare Datei beispiel starten:
$ ./beispiel
Ausführen des Programms
Wenn sowohl das Kompilieren als auch das Linken ohne Probleme erfolgt sind, werden Sie feststellen, dass das Programm tatsächlich funktioniert:
Guten Tag, die Zufallszahl ist: 64
Das war ... Python
Das Python-Skript läuft bislang völlig autonom, und es können keine Werte zwischen ihm und dem C-Programm ausgetauscht werden. Aber gerade die Interaktion mit dem Hauptprogramm macht die Qualität einer eingebetteten Skriptsprache aus.
37.3.2 Ein komplexeres Beispiel 

Im vorangegangenen Abschnitt haben Sie ein triviales Beispiel für die Einbettung von Python als Skriptsprache kennengelernt. Basierend auf diesen Ergebnissen können wir in diesem Abschnitt ein komplexeres Einsatzbeispiel angehen, in dem Werte über eine Funktionsschnittstelle geschickt bzw. entgegengenommen werden. Außerdem soll das C-Programm dazu in der Lage sein, eigene Funktionen zu definieren, die aus dem Python-Skript heraus aufgerufen werden können. Sie werden feststellen, dass auch dies dem Schreiben von Erweiterungen ähnelt.
Das folgende C-Programm lädt ein Python-Skript, das eine Funktion entscheide implementiert. Diese Funktion soll sich für einen von zwei übergebenen Strings entscheiden. Die Funktion könnte beispielsweise deshalb in ein Python-Skript ausgelagert worden sein, weil der Programmierer es dem Anwender ermöglichen will, die Funktion selbst zu implementieren und das Programm somit an die eigenen Bedürfnisse anzupassen.
Der Quellcode des Beispielprogramms sieht folgendermaßen aus:
#include <Python.h>
int main(int argc, char *argv[])
{
char *ergebnis;
PyObject *modul, *funk, *prm, *ret;
Py_Initialize();
PySys_SetPath(L".");
modul = PyImport_ImportModule("script");
if(modul)
{
funk = PyObject_GetAttrString(modul, "entscheide");
prm = Py_BuildValue("(yy)", "Hallo", "Welt");
ret = PyObject_CallObject(funk, prm);
ergebnis = PyBytes_AsString(ret);
printf("Das Skript hat sich fuer '%s' entschieden\n", ergebnis);
Py_DECREF(prm);
Py_DECREF(ret);
Py_DECREF(funk);
Py_DECREF(modul);
}
else
printf("Fehler: Modul nicht gefunden\n");
Py_Finalize();
}
In der Hauptfunktion main des C-Programms wird zunächst der Python-Interpreter durch Aufruf von Py_Initialize initialisiert. Danach wird durch die Funktion PySys_SetPath das lokale Programmverzeichnis als einziger Ordner festgelegt, aus dem Module importiert werden können. Beachten Sie, dass dieser Funktionsaufruf sowohl dem C- als auch dem Python-Programm verbietet, globale Module wie beispielsweise math einzubinden. Wenn Sie solche Module benötigen, dürfen Sie die import-Pfade nicht, wie es in diesem Beispiel geschehen ist, überschreiben, sondern Sie sollten sich den Pfad mit Py_GetPath holen, ihn um das Verzeichnis . erweitern und mit PySys_SetPath setzen. Beachten Sie, dass das lokale Programmverzeichnis standardmäßig nicht als import-Pfad eingetragen ist.
Durch Aufruf der Funktion PyImport_ImportModule wird ein Modul eingebunden und als PyObject-Pointer zurückgegeben. Wenn die entsprechenden Pfade festgelegt wurden, können sowohl lokale als auch globale Module mit dieser Funktion eingebunden werden. Im Folgenden prüfen wir, ob das Modul erfolgreich geladen wurde. Bei einem Misserfolg gibt die Funktion PyImport_ImportModule wie die meisten anderen Funktionen, die einen PyObject-Pointer zurückgeben, NULL zurück. Beachten Sie, dass es immer ratsam ist, die zurückgegebenen PyObject-Pointer auf NULL zu testen. Im Beispielprogramm wurde dies nur exemplarisch bei modul gemacht.
Nun beziehen wir durch Aufruf der Funktion PyObject_GetAttrString einen Pointer auf die Funktion entscheide des Moduls script. Um die Funktion aufrufen zu können, müssen wir die Funktionsparameter in Form eines Tupels übergeben. Dazu erzeugen wir mittels Py_BuildValue ein neues Tupel, das die beiden bytes-Strings "Hallo" und "Welt" enthält, von denen die Funktion entscheide einen auswählen soll.
Durch Aufruf der Funktion PyObject_CallObject wird die Funktion funk schließlich aufgerufen und ihr Rückgabewert ebenfalls in Form eines Pointers auf PyObject zurückgegeben. Da es sich bei dem Rückgabewert um einen bytes-String handelt, können wir diesen mit PyBytes_AsString zu einem C-String konvertieren und dann mit printf ausgeben.
Die in diesem Beispiel aufgerufene Python-Funktion entscheide sieht folgendermaßen aus und befindet sich in der Programmdatei script.py:
def entscheide(a, b):
return (a if min(a) < min(b) else b)
Die Funktion bekommt zwei Sequenzen a und b übergeben und gibt eine der beiden zurück. Die Entscheidung, welche der beiden Sequenzen zurückgegeben wird, hängt davon ab, welche das in der jeweiligen Ordnungsrelation kleinste Element enthält. Bei zwei Strings bzw. bytes-Strings wird beispielsweise derjenige zurückgegeben, der den alphabetisch kleinsten Buchstaben enthält.
Im nächsten Beispielprogramm soll es dem Python-Skript ermöglicht werden, bestimmte Funktionen des C-Programms aufzurufen. Es soll dem Skript also gewissermaßen eine API zur Verfügung gestellt werden, die es verwenden kann. Diese Idee liegt nicht nur gedanklich nah an den in Abschnitt 37.2, »Schreiben von Extensions«, besprochenen Extensions, sondern wird auch ganz ähnlich umgesetzt. Der Quelltext des Beispielprogramms sieht folgendermaßen aus:
#include <Python.h>
static PyObject *testfunktion(PyObject *self, PyObject *args)
{
int a, b;
if(!PyArg_ParseTuple(args, "ii", &a, &b))
return NULL;
return Py_BuildValue("i", a + b);
}
static PyMethodDef MethodTable[] =
{
{"testfunktion", testfunktion, METH_VARARGS, "Testfunktion"},
{NULL, NULL, 0, NULL}
};
static PyModuleDef APIModule =
{
PyModuleDef_HEAD_INIT,
"api", NULL, -1, MethodTable
};
static PyObject *PyInit_api()
{
return PyModule_Create(&APIModule);
}
int main(int argc, char *argv[])
{
FILE *f;
PyImport_AppendInittab("api", &PyInit_api);
Py_Initialize();
f = _Py_fopen("script.py", "r");
PyRun_SimpleFile(f, "script.py");
fclose(f);
Py_Finalize();
}
Zunächst wird die Funktion testfunktion definiert, die später dem Python-Skript über ein Modul namens api zur Verfügung gestellt werden soll. Im Beispiel berechnet die Funktion schlicht die Summe zweier ganzer Zahlen, die ihr als Parameter übergeben werden. Danach werden die MethodTable und die Moduldefinition erstellt, ganz als würden wir eine Erweiterung schreiben. Ebenfalls analog zu den Erweiterungen wird eine Initialisierungsfunktion für das Modul namens PyInit_api benötigt. Durch Aufruf der Funktion PyImport_AppendInittab wird unser Modul api in die Liste der verfügbaren Module eingetragen.
Schließlich brauchen wir nur noch die Funktion PyRun_SimpleFile aufzurufen, um das Python-Skript script.py zu interpretieren. Der Funktion müssen wir dabei ein geöffnetes Dateiobjekt übergeben.
Das Python-Skript, das von diesem C-Programm aufgerufen wird, kann beispielsweise folgendermaßen aussehen:
import api
print("Zwei plus zwei ist:", api.testfunktion(2, 2))