Python - Erweiterungsprogrammierung mit C.

Jeder Code, den Sie mit einer kompilierten Sprache wie C, C ++ oder Java schreiben, kann in ein anderes Python-Skript integriert oder importiert werden. Dieser Code wird als "Erweiterung" betrachtet.

Ein Python-Erweiterungsmodul ist nichts anderes als eine normale C-Bibliothek. Auf Unix-Computern enden diese Bibliotheken normalerweise mit.so(für gemeinsames Objekt). Auf Windows-Computern sehen Sie normalerweise.dll (für dynamisch verknüpfte Bibliothek).

Voraussetzungen für das Schreiben von Erweiterungen

Um mit dem Schreiben Ihrer Erweiterung zu beginnen, benötigen Sie die Python-Header-Dateien.

  • Auf Unix-Computern erfordert dies normalerweise die Installation eines entwicklerspezifischen Pakets wie python2.5-dev .

  • Windows-Benutzer erhalten diese Header als Teil des Pakets, wenn sie das binäre Python-Installationsprogramm verwenden.

Außerdem wird davon ausgegangen, dass Sie über gute Kenntnisse in C oder C ++ verfügen, um eine Python-Erweiterung mithilfe der C-Programmierung schreiben zu können.

Schauen Sie sich zuerst eine Python-Erweiterung an

Für Ihren ersten Blick auf ein Python-Erweiterungsmodul müssen Sie Ihren Code in vier Teile gruppieren:

  • Die Header-Datei Python.h .

  • Die C-Funktionen, die Sie als Schnittstelle Ihres Moduls verfügbar machen möchten.

  • Eine Tabelle, in der die Namen Ihrer Funktionen als Python-Entwickler den C-Funktionen im Erweiterungsmodul zugeordnet sind.

  • Eine Initialisierungsfunktion.

Die Header-Datei Python.h

Sie müssen die Python.h- Headerdatei in Ihre C-Quelldatei aufnehmen, damit Sie auf die interne Python-API zugreifen können, mit der Sie Ihr Modul in den Interpreter einbinden .

Stellen Sie sicher, dass Sie Python.h vor allen anderen Headern einfügen, die Sie möglicherweise benötigen. Sie müssen den Includes mit den Funktionen folgen, die Sie von Python aus aufrufen möchten.

Die C-Funktionen

Die Signaturen der C-Implementierung Ihrer Funktionen haben immer eine der folgenden drei Formen:

static PyObject *MyFunction( PyObject *self, PyObject *args );

static PyObject *MyFunctionWithKeywords(PyObject *self,
                                 PyObject *args,
                                 PyObject *kw);

static PyObject *MyFunctionWithNoArgs( PyObject *self );

Jede der vorhergehenden Deklarationen gibt ein Python-Objekt zurück. In Python gibt es keine void- Funktion wie in C. Wenn Sie nicht möchten, dass Ihre Funktionen einen Wert zurückgeben, geben Sie das C-Äquivalent von Python zurückNoneWert. Die Python-Header definieren ein Makro, Py_RETURN_NONE, das dies für uns erledigt.

Die Namen Ihrer C-Funktionen können beliebig sein, da sie außerhalb des Erweiterungsmoduls nie angezeigt werden. Sie sind als statische Funktion definiert .

Ihre C-Funktionen werden normalerweise benannt, indem Sie das Python-Modul und die Funktionsnamen miteinander kombinieren, wie hier gezeigt -

static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Do your stuff here. */
   Py_RETURN_NONE;
}

Dies ist eine Python-Funktion namens func innerhalb des Modulmoduls . Sie werden Zeiger auf Ihre C-Funktionen in die Methodentabelle für das Modul einfügen, das normalerweise als nächstes in Ihrem Quellcode enthalten ist.

Die Methodenzuordnungstabelle

Diese Methodentabelle ist ein einfaches Array von PyMethodDef-Strukturen. Diese Struktur sieht ungefähr so ​​aus -

struct PyMethodDef {
   char *ml_name;
   PyCFunction ml_meth;
   int ml_flags;
   char *ml_doc;
};

Hier ist die Beschreibung der Mitglieder dieser Struktur -

  • ml_name - Dies ist der Name der Funktion, die der Python-Interpreter anzeigt, wenn er in Python-Programmen verwendet wird.

  • ml_meth - Dies muss die Adresse einer Funktion sein, die eine der im vorherigen Abschnitt beschriebenen Signaturen hat.

  • ml_flags - Dies teilt dem Interpreter mit, welche der drei Signaturen ml_meth verwendet.

    • Dieses Flag hat normalerweise den Wert METH_VARARGS.

    • Dieses Flag kann mit METH_KEYWORDS bitweise ODER verknüpft werden, wenn Sie Schlüsselwortargumente in Ihre Funktion aufnehmen möchten.

    • Dies kann auch den Wert METH_NOARGS haben, der angibt, dass Sie keine Argumente akzeptieren möchten.

  • ml_doc - Dies ist die Dokumentzeichenfolge für die Funktion, die NULL sein kann, wenn Sie keine Lust haben, eine zu schreiben.

Diese Tabelle muss mit einem Sentinel abgeschlossen werden, der aus NULL- und 0-Werten für die entsprechenden Mitglieder besteht.

Beispiel

Für die oben definierte Funktion haben wir folgende Methodenzuordnungstabelle:

static PyMethodDef module_methods[] = {
   { "func", (PyCFunction)module_func, METH_NOARGS, NULL },
   { NULL, NULL, 0, NULL }
};

Die Initialisierungsfunktion

Der letzte Teil Ihres Erweiterungsmoduls ist die Initialisierungsfunktion. Diese Funktion wird vom Python-Interpreter beim Laden des Moduls aufgerufen. Es ist erforderlich, dass die Funktion benannt wirdinitModule, wobei Modul der Name des Moduls ist.

Die Initialisierungsfunktion muss aus der Bibliothek exportiert werden, die Sie erstellen möchten. Die Python-Header definieren PyMODINIT_FUNC so, dass sie die entsprechenden Beschwörungsformeln für die jeweilige Umgebung enthalten, in der wir kompilieren. Alles was Sie tun müssen, ist es beim Definieren der Funktion zu verwenden.

Ihre C-Initialisierungsfunktion hat im Allgemeinen die folgende Gesamtstruktur:

PyMODINIT_FUNC initModule() {
   Py_InitModule3(func, module_methods, "docstring...");
}

Hier ist die Beschreibung der Py_InitModule3- Funktion -

  • func - Dies ist die zu exportierende Funktion.

  • module_methods - Dies ist der oben definierte Name der Zuordnungstabelle.

  • docstring - Dies ist der Kommentar, den Sie in Ihrer Erweiterung abgeben möchten.

Alles zusammen sieht folgendermaßen aus:

#include <Python.h>

static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Do your stuff here. */
   Py_RETURN_NONE;
}

static PyMethodDef module_methods[] = {
   { "func", (PyCFunction)module_func, METH_NOARGS, NULL },
   { NULL, NULL, 0, NULL }
};

PyMODINIT_FUNC initModule() {
   Py_InitModule3(func, module_methods, "docstring...");
}

Beispiel

Ein einfaches Beispiel, das alle oben genannten Konzepte verwendet -

#include <Python.h>

static PyObject* helloworld(PyObject* self) {
   return Py_BuildValue("s", "Hello, Python extensions!!");
}

static char helloworld_docs[] =
   "helloworld( ): Any message you want to put here!!\n";

static PyMethodDef helloworld_funcs[] = {
   {"helloworld", (PyCFunction)helloworld, 
      METH_NOARGS, helloworld_docs},
      {NULL}
};

void inithelloworld(void) {
   Py_InitModule3("helloworld", helloworld_funcs,
                  "Extension module example!");
}

Hier wird die Py_BuildValue- Funktion verwendet, um einen Python-Wert zu erstellen. Speichern Sie den obigen Code in der Datei hello.c. Wir würden sehen, wie dieses Modul kompiliert und installiert wird, das aus dem Python-Skript aufgerufen wird.

Erstellen und Installieren von Erweiterungen

Das distutils- Paket macht es sehr einfach, Python-Module, sowohl reine Python- als auch Erweiterungsmodule, auf standardmäßige Weise zu verteilen. Module werden in Quellform verteilt und über ein Setup-Skript namens setup.py wie folgt erstellt und installiert .

Für das obige Modul müssen Sie das folgende Skript setup.py vorbereiten:

from distutils.core import setup, Extension
setup(name='helloworld', version='1.0',  \
      ext_modules=[Extension('helloworld', ['hello.c'])])

Verwenden Sie nun den folgenden Befehl, der alle erforderlichen Kompilierungs- und Verknüpfungsschritte mit den richtigen Compiler- und Linkerbefehlen und -flags ausführt, und kopieren Sie die resultierende dynamische Bibliothek in ein geeignetes Verzeichnis:

$ python setup.py install

Auf Unix-basierten Systemen müssen Sie diesen Befehl höchstwahrscheinlich als root ausführen, um über Berechtigungen zum Schreiben in das Site-Packages-Verzeichnis zu verfügen. Dies ist unter Windows normalerweise kein Problem.

Erweiterungen importieren

Sobald Sie Ihre Erweiterung installiert haben, können Sie diese Erweiterung wie folgt in Ihr Python-Skript importieren und aufrufen:

#!/usr/bin/python
import helloworld

print helloworld.helloworld()

Dies würde das folgende Ergebnis erzeugen -

Hello, Python extensions!!

Funktionsparameter übergeben

Da Sie höchstwahrscheinlich Funktionen definieren möchten, die Argumente akzeptieren, können Sie eine der anderen Signaturen für Ihre C-Funktionen verwenden. Die folgende Funktion, die eine bestimmte Anzahl von Parametern akzeptiert, würde beispielsweise folgendermaßen definiert:

static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Parse args and do something interesting here. */
   Py_RETURN_NONE;
}

Die Methodentabelle mit einem Eintrag für die neue Funktion würde folgendermaßen aussehen:

static PyMethodDef module_methods[] = {
   { "func", (PyCFunction)module_func, METH_NOARGS, NULL },
   { "func", module_func, METH_VARARGS, NULL },
   { NULL, NULL, 0, NULL }
};

Mit der API- Funktion PyArg_ParseTuple können Sie die Argumente aus dem einen PyObject-Zeiger extrahieren, der an Ihre C-Funktion übergeben wurde.

Das erste Argument für PyArg_ParseTuple ist das Argument args. Dies ist das Objekt, das Sie analysieren werden . Das zweite Argument ist eine Formatzeichenfolge, die die Argumente so beschreibt, wie Sie sie erwarten. Jedes Argument wird wie folgt durch ein oder mehrere Zeichen in der Formatzeichenfolge dargestellt.

static PyObject *module_func(PyObject *self, PyObject *args) {
   int i;
   double d;
   char *s;

   if (!PyArg_ParseTuple(args, "ids", &i, &d, &s)) {
      return NULL;
   }
   
   /* Do something interesting here. */
   Py_RETURN_NONE;
}

Wenn Sie die neue Version Ihres Moduls kompilieren und importieren, können Sie die neue Funktion mit einer beliebigen Anzahl von Argumenten eines beliebigen Typs aufrufen.

module.func(1, s="three", d=2.0)
module.func(i=1, d=2.0, s="three")
module.func(s="three", d=2.0, i=1)

Sie können sich wahrscheinlich noch mehr Variationen einfallen lassen.

Die PyArg_ParseTuple- Funktion

Hier ist die Standardsignatur für PyArg_ParseTuple Funktion -

int PyArg_ParseTuple(PyObject* tuple,char* format,...)

Diese Funktion gibt 0 für Fehler und einen Wert ungleich 0 für Erfolg zurück. Tupel ist das PyObject *, das das zweite Argument der C-Funktion war. Hier ist das Format eine C-Zeichenfolge, die obligatorische und optionale Argumente beschreibt.

Hier ist eine Liste der Formatcodes für PyArg_ParseTuple Funktion -

Code Typ C. Bedeutung
c verkohlen Eine Python-Zeichenfolge der Länge 1 wird zu einem C-Zeichen.
d doppelt Ein Python-Float wird zu einem C-Double.
f schweben Ein Python-Float wird zu einem C-Float.
ich int Ein Python-Int wird zu einem C-Int.
l lange Ein Python-Int wird zu einem C-Long.
L. lang Lang Ein Python-Int wird zu einem C-Long-Long
Ö PyObject * Ruft einen nicht NULL ausgeliehenen Verweis auf das Python-Argument ab.
s verkohlen* Python-String ohne eingebettete Nullen in C char *.
s # char * + int Beliebige Python-Zeichenfolge mit C-Adresse und Länge.
t # char * + int Schreibgeschützter Einzelsegmentpuffer auf C-Adresse und Länge.
u Py_UNICODE * Python Unicode ohne eingebettete Nullen für C.
u # Py_UNICODE * + int Beliebige Python Unicode C-Adresse und -Länge.
w # char * + int Lesen / Schreiben eines Einzelsegmentpuffers auf C-Adresse und Länge.
z verkohlen* Akzeptiert wie s auch None (setzt C char * auf NULL).
z # char * + int Akzeptiert wie s # auch None (setzt C char * auf NULL).
(...) gemäß ... Eine Python-Sequenz wird als ein Argument pro Element behandelt.
|   Die folgenden Argumente sind optional.
::   Formatieren Sie das Ende, gefolgt vom Funktionsnamen für Fehlermeldungen.
;;   Formatieren Sie das Ende, gefolgt vom gesamten Text der Fehlermeldung.

Werte zurückgeben

Py_BuildValue nimmt eine Formatzeichenfolge an, ähnlich wie PyArg_ParseTuple . Anstatt die Adressen der Werte zu übergeben, die Sie erstellen, übergeben Sie die tatsächlichen Werte. Hier ist ein Beispiel, das zeigt, wie eine Add-Funktion implementiert wird:

static PyObject *foo_add(PyObject *self, PyObject *args) {
   int a;
   int b;

   if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
      return NULL;
   }
   return Py_BuildValue("i", a + b);
}

So würde es aussehen, wenn es in Python implementiert würde -

def add(a, b):
   return (a + b)

Sie können zwei Werte von Ihrer Funktion wie folgt zurückgeben. Dies wird mithilfe einer Liste in Python behoben.

static PyObject *foo_add_subtract(PyObject *self, PyObject *args) {
   int a;
   int b;

   if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
      return NULL;
   }
   return Py_BuildValue("ii", a + b, a - b);
}

So würde es aussehen, wenn es in Python implementiert würde -

def add_subtract(a, b):
   return (a + b, a - b)

Die Py_BuildValue- Funktion

Hier ist die Standardsignatur für Py_BuildValue Funktion -

PyObject* Py_BuildValue(char* format,...)

Hier ist das Format eine C-Zeichenfolge, die das zu erstellende Python-Objekt beschreibt. Die folgenden Argumente von Py_BuildValue sind C-Werte, aus denen das Ergebnis erstellt wird. Das PyObject * -Ergebnis ist eine neue Referenz.

In der folgenden Tabelle sind die häufig verwendeten Codezeichenfolgen aufgeführt, von denen null oder mehr im Zeichenfolgenformat verknüpft sind.

Code Typ C. Bedeutung
c verkohlen AC char wird zu einer Python-Zeichenfolge mit der Länge 1.
d doppelt AC Double wird zu einem Python-Float.
f schweben AC float wird zu einem Python float.
ich int AC int wird zu Python int.
l lange AC wird lange zu einem Python-Int.
N. PyObject * Übergibt ein Python-Objekt und stiehlt eine Referenz.
Ö PyObject * Übergibt ein Python-Objekt und ERHÖHT es wie gewohnt.
Ö& konvertieren + nichtig * Willkürliche Umwandlung
s verkohlen* C 0-terminiertes Zeichen * für Python-Zeichenfolge oder NULL für Keine.
s # char * + int C char * und Länge zu Python-String oder NULL zu None.
u Py_UNICODE * C-weite, nullterminierte Zeichenfolge für Python Unicode oder NULL für Keine.
u # Py_UNICODE * + int C-weite Zeichenfolge und Länge zu Python Unicode oder NULL zu Keine.
w # char * + int Lesen / Schreiben eines Einzelsegmentpuffers auf C-Adresse und Länge.
z verkohlen* Akzeptiert wie s auch None (setzt C char * auf NULL).
z # char * + int Akzeptiert wie s # auch None (setzt C char * auf NULL).
(...) gemäß ... Erstellt ein Python-Tupel aus C-Werten.
[...] gemäß ... Erstellt eine Python-Liste aus C-Werten.
{...} gemäß ... Erstellt ein Python-Wörterbuch aus C-Werten, alternierenden Schlüsseln und Werten.

Code {...} erstellt Wörterbücher aus einer geraden Anzahl von C-Werten, abwechselnd Schlüsseln und Werten. Zum Beispiel gibt Py_BuildValue ("{issi}", 23, "zig", "zag", 42) ein Wörterbuch wie Pythons {23: 'zig', 'zag': 42} zurück.