1. Python in eine andere Anwendung einbetten¶
Die vorherigen Kapitel behandelten, wie man Python erweitert, d.h. wie man die Funktionalität von Python erweitert, indem man eine Bibliothek von C-Funktionen daran anbindet. Es ist auch möglich, den umgekehrten Weg zu gehen: die eigene C/C++-Anwendung zu bereichern, indem man Python darin einbettet. Das Einbetten verleiht der Anwendung die Fähigkeit, einige Funktionalitäten der Anwendung in Python statt in C oder C++ zu implementieren. Dies kann für viele Zwecke genutzt werden; ein Beispiel wäre, Benutzern zu ermöglichen, die Anwendung an ihre Bedürfnisse anzupassen, indem sie einige Skripte in Python schreiben. Man kann es auch selbst nutzen, wenn sich einige Funktionalitäten leichter in Python schreiben lassen.
Das Einbetten von Python ähnelt dem Erweitern, aber nicht ganz. Der Unterschied besteht darin, dass beim Erweitern von Python das Hauptprogramm der Anwendung immer noch der Python-Interpreter ist, während beim Einbetten von Python das Hauptprogramm nichts mit Python zu tun haben muss – stattdessen rufen einige Teile der Anwendung gelegentlich den Python-Interpreter auf, um Python-Code auszuführen.
Wenn Sie also Python einbetten, stellen Sie Ihr eigenes Hauptprogramm bereit. Eines der Dinge, die dieses Hauptprogramm tun muss, ist die Initialisierung des Python-Interpreters. Zumindest müssen Sie die Funktion Py_Initialize() aufrufen. Es gibt optionale Aufrufe, um Kommandozeilenargumente an Python zu übergeben. Später können Sie den Interpreter von jedem Teil der Anwendung aus aufrufen.
Es gibt verschiedene Möglichkeiten, den Interpreter aufzurufen: Sie können einen String mit Python-Anweisungen an PyRun_SimpleString() übergeben, oder Sie können einen stdio-Dateizeiger und einen Dateinamen (nur zur Identifizierung in Fehlermeldungen) an PyRun_SimpleFile() übergeben. Sie können auch die in den vorherigen Kapiteln beschriebenen Low-Level-Operationen aufrufen, um Python-Objekte zu konstruieren und zu verwenden.
Siehe auch
- Python/C API Referenzhandbuch
Die Details der C-Schnittstelle von Python sind in diesem Handbuch beschrieben. Hier finden Sie viele notwendige Informationen.
1.1. Sehr High-Level-Einbettung¶
Die einfachste Form der Einbettung von Python ist die Verwendung der sehr High-Level-Schnittstelle. Diese Schnittstelle ist dazu gedacht, ein Python-Skript auszuführen, ohne direkt mit der Anwendung interagieren zu müssen. Dies kann zum Beispiel verwendet werden, um eine Operation auf einer Datei durchzufü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);
}
Hinweis
#define PY_SSIZE_T_CLEAN wurde verwendet, um anzuzeigen, dass Py_ssize_t in einigen APIs anstelle von int verwendet werden soll. Seit Python 3.13 ist dies nicht mehr notwendig, wir behalten es hier jedoch zur Abwärtskompatibilität bei. Eine Beschreibung dieses Makros finden Sie unter Strings und Puffer.
Das Setzen von PyConfig.program_name sollte vor Py_InitializeFromConfig() aufgerufen werden, um den Interpreter über die Pfade zu den Python-Laufzeitbibliotheken zu informieren. Als Nächstes wird der Python-Interpreter mit Py_Initialize() initialisiert, gefolgt von der Ausführung eines fest codierten Python-Skripts, das Datum und Uhrzeit ausgibt. Danach beendet der Aufruf von Py_FinalizeEx() den Interpreter, gefolgt vom Ende des Programms. In einem echten Programm möchten Sie vielleicht das Python-Skript aus einer anderen Quelle beziehen, z. B. aus einer Texteditor-Routine, einer Datei oder einer Datenbank. Das Abrufen des Python-Codes aus einer Datei kann besser mit der Funktion PyRun_SimpleFile() erfolgen, was Ihnen die Mühe der Speicherallokation und des Ladens des Dateiinhalts erspart.
1.2. Jenseits der Very High-Level-Einbettung: Ein Überblick¶
Die High-Level-Schnittstelle ermöglicht es Ihnen, beliebige Python-Code-Teile aus Ihrer Anwendung auszuführen, aber der Austausch von Datenwerten ist, gelinde gesagt, ziemlich umständlich. Wenn Sie das möchten, sollten Sie Low-Level-Aufrufe verwenden. Auf Kosten von mehr C-Code können Sie fast alles erreichen.
Es ist erwähnenswert, dass das Erweitern von Python und das Einbetten von Python trotz unterschiedlicher Absichten ziemlich die gleiche Tätigkeit sind. Die meisten Themen, die in den vorherigen Kapiteln besprochen wurden, sind immer noch gültig. Um dies zu veranschaulichen, betrachten wir, was der Erweiterungscode von Python nach C wirklich tut:
Datenwerte von Python nach C konvertieren,
einen Funktionsaufruf an eine C-Routine unter Verwendung der konvertierten Werte ausführen und
Datenwerte vom Aufruf von C nach Python konvertieren.
Beim Einbetten von Python tut der Schnittstellencode Folgendes:
Datenwerte von C nach Python konvertieren,
einen Funktionsaufruf an eine Python-Schnittstellenroutine unter Verwendung der konvertierten Werte ausführen und
Datenwerte vom Aufruf von Python nach C konvertieren.
Wie Sie sehen, werden die Datenkonvertierungsschritte einfach vertauscht, um die unterschiedliche Richtung des sprachübergreifenden Transfers zu berücksichtigen. Der einzige Unterschied ist die Routine, die Sie zwischen den beiden Datenkonvertierungen aufrufen. Beim Erweitern rufen Sie eine C-Routine auf, beim Einbetten rufen Sie eine Python-Routine auf.
Dieses Kapitel wird nicht behandeln, wie Daten von Python nach C und umgekehrt konvertiert werden. Auch die korrekte Verwendung von Referenzen und der Umgang mit Fehlern werden als verstanden vorausgesetzt. Da sich diese Aspekte nicht vom Erweitern des Interpreters unterscheiden, können Sie für die erforderlichen Informationen auf frühere Kapitel verweisen.
1.3. Reines Einbetten¶
Das erste Programm zielt darauf ab, eine Funktion in einem Python-Skript auszuführen. Wie im Abschnitt über die Very High-Level-Schnittstelle interagiert der Python-Interpreter nicht direkt mit der Anwendung (aber das wird sich im nächsten Abschnitt ändern).
Der Code zum Ausführen einer Funktion, die in einem Python-Skript definiert ist, 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 unter Verwendung von argv[1] und ruft die Funktion auf, die in argv[2] benannt ist. Ihre ganzzahligen Argumente sind die anderen Werte des argv-Arrays. Wenn Sie dieses Programm kompilieren und linken (nennen wir die fertige ausführbare Datei call) und es verwenden, um ein Python-Skript auszuführen, wie z.B.:
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 sein:
$ call multiply multiply 3 2
Will compute 3 times 2
Result of call: 6
Obwohl das Programm für seine Funktionalität recht umfangreich ist, ist der Großteil des Codes für die Datenkonvertierung zwischen Python und C sowie für die Fehlerberichterstattung bestimmt. Der interessante Teil in Bezug auf das Einbetten von Python beginnt mit
Py_Initialize();
pName = PyUnicode_DecodeFSDefault(argv[1]);
/* Error checking of pName left out */
pModule = PyImport_Import(pName);
Nach der Initialisierung des Interpreters wird das Skript mit PyImport_Import() geladen. Diese Routine benötigt einen Python-String als Argument, der mit der Datenkonvertierungsroutine PyUnicode_DecodeFSDefault() erstellt wird.
pFunc = PyObject_GetAttrString(pModule, argv[2]);
/* pFunc is a new reference */
if (pFunc && PyCallable_Check(pFunc)) {
...
}
Py_XDECREF(pFunc);
Sobald das Skript geladen ist, wird der gesuchte Name mit PyObject_GetAttrString() abgerufen. Wenn der Name existiert und das zurückgegebene Objekt aufrufbar ist, können Sie sicher davon ausgehen, dass es sich um eine Funktion handelt. Das Programm fährt dann damit fort, ein Tupel von Argumenten wie üblich zu erstellen. Der Aufruf der Python-Funktion erfolgt dann mit
pValue = PyObject_CallObject(pFunc, pArgs);
Nach der Rückgabe der Funktion ist pValue entweder NULL oder es enthält eine Referenz auf den Rückgabewert der Funktion. Achten Sie darauf, die Referenz nach der Untersuchung des Wertes freizugeben.
1.4. Eingebettetes Python erweitern¶
Bis jetzt hatte der eingebettete Python-Interpreter keinen Zugriff auf Funktionalitäten der Anwendung selbst. Die Python-API ermöglicht dies, indem der eingebettete Interpreter erweitert wird. Das heißt, der eingebettete Interpreter wird mit Routinen erweitert, die von der Anwendung bereitgestellt werden. Auch wenn es komplex klingt, ist es nicht so schlimm. Vergessen Sie einfach für eine Weile, dass die Anwendung den Python-Interpreter startet. Betrachten Sie stattdessen die Anwendung als eine Menge von Unterroutinen und schreiben Sie etwas Glue-Code, der Python den Zugriff auf diese Routinen ermöglicht, so wie Sie eine normale Python-Erweiterung schreiben würden. Zum Beispiel:
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ügen Sie den obigen Code direkt oberhalb der Funktion main() ein. Fügen Sie 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 Dinge tun wie:
import emb
print("Number of arguments", emb.numargs())
In einer echten Anwendung werden die Methoden eine API der Anwendung für Python bereitstellen.
1.5. Python in C++ einbetten¶
Es ist auch möglich, Python in ein C++-Programm einzubetten; wie genau dies geschieht, hängt von den Details des verwendeten C++-Systems ab; im Allgemeinen müssen Sie das Hauptprogramm in C++ schreiben und den C++-Compiler verwenden, um Ihr Programm zu kompilieren und zu linken. Es ist nicht notwendig, Python selbst mit C++ neu zu kompilieren.
1.6. Kompilieren und Linken unter Unix-ähnlichen Systemen¶
Es ist nicht unbedingt trivial, die richtigen Flags für Ihren Compiler (und Linker) zu finden, um den Python-Interpreter in Ihre Anwendung einzubetten, insbesondere weil Python Bibliotheksmodule laden muss, die als C-dynamische Erweiterungen (.so-Dateien) implementiert sind, die gegen es gelinkt sind.
Um die erforderlichen Compiler- und Linker-Flags zu ermitteln, können Sie das Skript pythonX.Y-config ausführen, das als Teil des Installationsprozesses generiert wird (ein Skript namens python3-config kann ebenfalls verfügbar sein). Dieses Skript hat mehrere Optionen, von denen die folgenden für Sie direkt nützlich sein werden:
pythonX.Y-config --cflagsgibt Ihnen die empfohlenen Flags beim 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 --embedgibt Ihnen die empfohlenen Flags beim Linken.$ /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
Hinweis
Um Verwirrung zwischen mehreren Python-Installationen (und insbesondere zwischen dem System-Python und Ihrem eigenen kompilierten Python) zu vermeiden, wird empfohlen, den absoluten Pfad zu pythonX.Y-config zu verwenden, wie im obigen Beispiel.
Wenn dieses Verfahren für Sie nicht funktioniert (es ist nicht garantiert, dass es für alle Unix-ähnlichen Plattformen funktioniert; wir begrüßen jedoch Fehlerberichte), müssen Sie die Dokumentation Ihres Systems zur dynamischen Verknüpfung lesen und/oder die Makefile von Python untersuchen (verwenden Sie sysconfig.get_makefile_filename(), um deren Speicherort zu finden) und die Kompilierungsoptionen. In diesem Fall ist das Modul sysconfig ein nützliches Werkzeug, um die Konfigurationswerte programmatisch zu extrahieren, die Sie kombinieren möchten. Zum Beispiel:
>>> import sysconfig
>>> sysconfig.get_config_var('LIBS')
'-lpthread -ldl -lutil'
>>> sysconfig.get_config_var('LINKFORSHARED')
'-Xlinker -export-dynamic'