Python in eine andere Anwendung einbetten

In den vorangegangenen Kapiteln ging es darum, wie du Python erweiterst, also wie du den Funktionsumfang von Python durch Einbinden einer Bibliothek mit C-Funktionen erweiterst. Es geht aber auch umgekehrt: Du kannst deine C/C++-Anwendung bereichern, indem du Python darin einbettest. Durch die Einbettung erhält deine Anwendung die Möglichkeit, einen Teil ihrer Funktionalität nicht in C oder C++, sondern in Python umzusetzen. Das lässt sich für vieles nutzen. Ein Beispiel wäre, Nutzern zu erlauben, die Anwendung an ihre Bedürfnisse anzupassen, indem sie ein paar Skripte in Python schreiben. Du kannst es auch selbst nutzen, wenn sich ein Teil der Funktionalität in Python leichter schreiben lässt.

Das Einbetten von Python ähnelt der Erweiterung von Python, ist aber nicht ganz dasselbe. Der Unterschied besteht darin, dass bei einer Erweiterung von Python der Python-Interpreter weiterhin das Hauptprogramm der Anwendung ist, während bei der Einbettung von Python das Hauptprogramm möglicherweise gar nichts mit Python zu tun hat – stattdessen rufen bestimmte Teile der Anwendung gelegentlich den Python-Interpreter auf, um Python-Code auszuführen.

Wenn du Python einbettest, stellst du also dein eigenes Hauptprogramm bereit. Eine der Aufgaben dieses Hauptprogramms ist es, den Python-Interpreter zu initialisieren. Mindestens musst du die Funktion Py_Initialize() aufrufen. Es gibt wahlfreie Aufrufe, um Befehlszeilenargumente an Python zu übergeben. Anschließend kannst du den Interpreter von jeder Stelle der Anwendung aus aufrufen.

Es gibt verschiedene Möglichkeiten, den Interpreter aufzurufen: Du kannst eine Zeichenkette mit Python-Anweisungen an PyRun_SimpleString() übergeben oder einen stdio-Dateizeiger und einen Dateinamen (der ausschließlich der Erkennung in Fehlermeldungen dient) an PyRun_SimpleFile(). Du kannst auch die in den vorangegangenen Kapiteln beschriebenen Operationen auf niedrigerer Ebene aufrufen, um Python-Objekte aufzubauen und zu verwenden.

Siehe auch

Python/C-API-Referenzhandbuch

Die Einzelheiten zur C-Schnittstelle von Python sind in diesem Handbuch beschrieben. Hier findest du zahlreiche wichtige Informationen.

Einbettung auf sehr hoher Ebene

Die einfachste Form, Python einzubetten, ist die Schnittstelle auf sehr hoher Ebene. Diese Schnittstelle ist dafür gedacht, ein Python-Skript auszuführen, ohne unmittelbar mit der Anwendung zusammenzuarbeiten. Damit lässt sich zum Beispiel eine Operation auf einer Datei ausführen.

#define PY_SSIZE_T_CLEAN
#include <Python.h>

int
main(int argc, char *argv[])
{
    PyStatus status;
    PyConfig config;
    PyConfig_InitPythonConfig(&config);

    /* optional but recommended */
    status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]);
    if (PyStatus_Exception(status)) {
        goto exception;
    }

    status = Py_InitializeFromConfig(&config);
    if (PyStatus_Exception(status)) {
        goto exception;
    }
    PyConfig_Clear(&config);

    PyRun_SimpleString("from time import time,ctime\n"
                       "print('Today is', ctime(time()))\n");
    if (Py_FinalizeEx() < 0) {
        exit(120);
    }
    return 0;

  exception:
     PyConfig_Clear(&config);
     Py_ExitStatusException(status);
}

Bemerkung

#define PY_SSIZE_T_CLEAN wurde verwendet, um darauf hinzuweisen, dass in einigen APIs Py_ssize_t anstelle von int verwendet werden sollte. Seit Python 3.13 ist dies nicht mehr erforderlich, wir behalten es jedoch aus Gründen der Abwärtskompatibilität bei. Eine Beschreibung dieses Makros findest du unter Zeichenketten und Puffer.

Setting PyConfig.program_name should be called before Py_InitializeFromConfig() to inform the interpreter about paths to Python run-time libraries. Next, the Python interpreter is initialized with Py_InitializeFromConfig(), followed by the execution of a hard-coded Python script that prints the date and time. Afterwards, the Py_FinalizeEx() call shuts the interpreter down, followed by the end of the program. In a real program, you may want to get the Python script from another source, perhaps a text-editor routine, a file, or a database. Getting the Python code from a file can better be done by using the PyRun_SimpleFile() function, which saves you the trouble of allocating memory space and loading the file contents.

Über die Einbettung auf sehr hoher Ebene hinaus: Ein Überblick

Die Schnittstelle auf hoher Ebene gibt dir die Möglichkeit, beliebige Stücke Python-Code aus deiner Anwendung heraus auszuführen, der Austausch von Datenwerten ist damit aber, gelinde gesagt, recht umständlich. Willst du das, solltest du Aufrufe auf niedrigerer Ebene verwenden. Um den Preis, mehr C-Code schreiben zu müssen, lässt sich damit fast alles erreichen.

Es sei darauf hingewiesen, dass die Erweiterung von Python und die Einbettung von Python trotz unterschiedlicher Zielsetzung im Grunde genommen dasselbe sind. Die meisten in den vorangegangenen Kapiteln behandelten Themen sind nach wie vor gültig. Um dies zu veranschaulichen, betrachten wir einmal, was der Erweiterungscode von Python nach C tatsächlich bewirkt:

  1. Datenwerte von Python nach C umwandeln,

  2. mit den umgewandelten Werten eine C-Routine aufrufen und

  3. die Datenwerte aus dem Aufruf von C nach Python umwandeln.

Bei der Einbettung von Python führt der Schnittstellencode Folgendes aus:

  1. Datenwerte von C nach Python umwandeln,

  2. mit den umgewandelten Werten eine Python-Schnittstellenroutine aufrufen und

  3. die Datenwerte aus dem Aufruf von Python nach C umwandeln.

Wie du siehst, sind die Schritte der Datenumwandlung einfach vertauscht, weil die sprachübergreifende Übertragung in die andere Richtung läuft. Der einzige Unterschied ist die Routine, die du zwischen den beiden Datenumwandlungen aufrufst: beim Erweitern eine C-Routine, beim Einbetten eine Python-Routine.

In diesem Kapitel wird nicht darauf eingegangen, wie Daten von Python nach C und umgekehrt konvertiert werden. Außerdem wird vorausgesetzt, dass du mit der korrekten Verwendung von Referenzen und dem Umgang mit Fehlern vertraut bist. Da sich diese Aspekte nicht von der Erweiterung des Interpreters unterscheiden, findest du die erforderlichen Informationen in den vorherigen Kapiteln.

Reine Einbettung

Das erste Programm soll eine Funktion in einem Python-Skript ausführen. Wie im Abschnitt über die Schnittstelle auf sehr hoher Ebene arbeitet der Python-Interpreter nicht unmittelbar mit der Anwendung zusammen (das ändert sich im nächsten Abschnitt).

Der Code zum Ausführen einer in einem Python-Skript definierten Funktion lautet:

#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) {
        fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
        return 1;
    }

    Py_Initialize();
    pName = PyUnicode_DecodeFSDefault(argv[1]);
    /* Error checking of pName left out */

    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if (pModule != NULL) {
        pFunc = PyObject_GetAttrString(pModule, argv[2]);
        /* pFunc is a new reference */

        if (pFunc && PyCallable_Check(pFunc)) {
            pArgs = PyTuple_New(argc - 3);
            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;
                }
                /* pValue reference stolen here: */
                PyTuple_SetItem(pArgs, i, pValue);
            }
            pValue = PyObject_CallObject(pFunc, pArgs);
            Py_DECREF(pArgs);
            if (pValue != NULL) {
                printf("Result of call: %ld\n", PyLong_AsLong(pValue));
                Py_DECREF(pValue);
            }
            else {
                Py_DECREF(pFunc);
                Py_DECREF(pModule);
                PyErr_Print();
                fprintf(stderr,"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 120;
    }
    return 0;
}

Dieser Code lädt ein Python-Skript über argv[1] und ruft die unter argv[2] genannte Funktion auf. Seine ganzzahligen Argumente sind die übrigen Werte des Arrays argv. Wenn du dieses Programm kompilierst und verknüpfst (nennen wir die fertige ausführbare Datei call) und es zur Ausführung eines Python-Skripts verwendest, wie zum Beispiel:

def multiply(a,b):
    print("Will compute", a, "times", b)
    c = 0
    for i in range(0, a):
        c = c + b
    return c

Dann sollte das Ergebnis lauten:

$ call multiply multiply 3 2
Berechnet 3 mal 2
Ergebnis des Aufrufs: 6

Obwohl das Programm für seinen Funktionsumfang recht groß ist, dient der größte Teil des Codes der Datenumwandlung zwischen Python und C sowie der Fehlerbehandlung. Der für die Einbettung von Python interessante Teil beginnt mit

Py_Initialize();
pName = PyUnicode_DecodeFSDefault(argv[1]);
/* Fehlerprüfung von pName ausgelassen */
pModule = PyImport_Import(pName);

Nach der Initialisierung des Interpreters wird das Skript mit der Funktion PyImport_Import() geladen. Diese Routine benötigt als Argument eine Python-Zeichenkette, die mithilfe der Datenkonvertierungsroutine PyUnicode_DecodeFSDefault() erstellt wird.

pFunc = PyObject_GetAttrString(pModule, argv[2]);
/* pFunc ist eine neue Referenz */

if (pFunc && PyCallable_Check(pFunc)) {
    ...
}
Py_XDECREF(pFunc);

Sobald das Skript geladen ist, wird der gesuchte Name mit PyObject_GetAttrString() geholt. Existiert der Name und ist das zurückgegebene Objekt aufrufbar, kannst du davon ausgehen, dass es eine Funktion ist. Das Programm baut dann wie gewohnt ein Tupel aus Argumenten auf. Der Aufruf der Python-Funktion geschieht anschließend so:

pValue = PyObject_CallObject(pFunc, pArgs);

Nach der Rückkehr der Funktion ist pValue entweder NULL oder enthält einen Verweis auf den Rückgabewert der Funktion. Achte darauf, den Verweis nach der Überprüfung des Werts freizugeben.

Eingebettetes Python erweitern

Bislang hatte der eingebettete Python-Interpreter keinen Zugriff auf Funktionalität der Anwendung selbst. Die Python-API ermöglicht das, indem der eingebettete Interpreter erweitert wird. Das heißt, der eingebettete Interpreter wird um Routinen erweitert, die die Anwendung bereitstellt. Das klingt aufwendig, ist aber gar nicht so schlimm. Vergiss für einen Moment einfach, dass die Anwendung den Python-Interpreter startet. Betrachte die Anwendung stattdessen als eine Reihe von Unterprogrammen und schreibe etwas „Klebecode“, der Python Zugriff auf diese Routinen gibt — genau so, wie du ein gewöhnliches Python-Erweiterungsmodul schreiben würdest.

static int numargs=0;

/* Return the number of arguments of the application command line */
static PyObject*
emb_numargs(PyObject *self, PyObject *args)
{
    if(!PyArg_ParseTuple(args, ":numargs"))
        return NULL;
    return PyLong_FromLong(numargs);
}

static PyMethodDef emb_module_methods[] = {
    {"numargs", emb_numargs, METH_VARARGS,
     "Return the number of arguments received by the process."},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef emb_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "emb",
    .m_size = 0,
    .m_methods = emb_module_methods,
};

static PyObject*
PyInit_emb(void)
{
    return PyModuleDef_Init(&emb_module);
}

Füge den obigen Code direkt vor der Funktion main() ein. Füge außerdem die folgenden beiden Anweisungen vor dem Aufruf von Py_Initialize() ein:

numargs = argc;
PyImport_AppendInittab("emb", &PyInit_emb);

Diese beiden Zeilen initialisieren die Variable numargs und machen die Funktion emb.numargs() für den eingebetteten Python-Interpreter zugänglich. Mit diesen Erweiterungen kann das Python-Skript zum Beispiel Folgendes tun:

import emb
print("Number of arguments", emb.numargs())

In einer realen Anwendung stellen die Methoden eine API der Anwendung für Python bereit.

Python in C++ einbetten

Es ist auch möglich, Python in ein C++-Programm einzubetten. Wie das genau geschieht, hängt von den Einzelheiten des verwendeten C++-Systems ab. Im Allgemeinen musst du das Hauptprogramm in C++ schreiben und den C++-Compiler verwenden, um dein Programm zu kompilieren und zu verknüpfen. Python selbst muss dafür nicht mit C++ neu kompiliert werden.

Kompilieren und Verknüpfen unter Unix-ähnlichen Systemen

Es ist nicht unbedingt einfach, die richtigen Flags zu finden, die du deinem Compiler (und Linker) übergeben musst, um den Python-Interpreter in deine Anwendung einzubetten — vor allem, weil Python Bibliotheksmodule laden muss, die als dynamische C-Erweiterungen (.so-Dateien) umgesetzt und gegen ihn verknüpft sind.

Um die erforderlichen Compiler- und Linker-Flags zu ermitteln, kannst du das Skript pythonX.Y-config ausführen, das im Rahmen des Installationsvorgangs erstellt wird (möglicherweise steht auch ein Skript namens python3-config zur Verfügung). Dieses Skript verfügt über mehrere Optionen, von denen die folgenden für dich unmittelbar von Nutzen sind:

  • pythonX.Y-config --cflags nennt dir die empfohlenen Flags zum Kompilieren:

    $ /opt/bin/python3.11-config --cflags
    -I/opt/include/python3.11 -I/opt/include/python3.11 -Wsign-compare  -DNDEBUG -g -fwrapv -O3 -Wall
    
  • pythonX.Y-config --ldflags --embed nennt dir die empfohlenen Flags zum Verknüpfen:

    $ /opt/bin/python3.11-config --ldflags --embed
    -L/opt/lib/python3.11/config-3.11-x86_64-linux-gnu -L/opt/lib -lpython3.11 -lpthread -ldl  -lutil -lm
    

Bemerkung

Um Verwechslungen zwischen mehreren Python-Installationen (und insbesondere zwischen dem System-Python und deinem selbst kompilierten Python) zu vermeiden, wird empfohlen, den absoluten Pfad zu pythonX.Y-config zu verwenden, wie im obigen Beispiel gezeigt.

Sollte dieses Verfahren bei dir nicht funktionieren (es ist nicht garantiert, dass es auf allen Unix-ähnlichen Plattformen funktioniert, über Fehlermeldungen freuen wir uns aber), musst du die Dokumentation deines Systems zum dynamischen Verknüpfen lesen und/oder das Makefile von Python (den Speicherort findest du mit sysconfig.get_makefile_filename()) sowie die Kompilierungsoptionen durchsehen. In diesem Fall ist das Modul sysconfig ein nützliches Werkzeug, um die Konfigurationswerte, die du zusammensetzen willst, programmgesteuert auszulesen. Zum Beispiel:

>>> import sysconfig
>>> sysconfig.get_config_var('LIBS')
'-lpthread -ldl  -lutil'
>>> sysconfig.get_config_var('LINKFORSHARED')
'-Xlinker -export-dynamic'