Python 3 - 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, die die Namen Ihrer Funktionen als Python-Entwickler abbildet, sieht sie als C-Funktionen im Erweiterungsmodul.

  • 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 ist die Adresse einer Funktion mit einer der im vorherigen Abschnitt beschriebenen Signaturen.

  • 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 die 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 von 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 es so 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 der Quellform verteilt, über ein Setup-Skript namens setup.py as 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 Erweiterungen installiert haben, können Sie diese Erweiterung wie folgt in Ihr Python-Skript importieren und aufrufen:

Beispiel

#!/usr/bin/python3
import helloworld

print helloworld.helloworld()

Ausgabe

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. Beispielsweise würde die folgende Funktion, die eine bestimmte Anzahl von Parametern akzeptiert, wie folgt 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;
}

Ausgabe

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 die 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 die PyArg_ParseTuple Funktion -

CodeTyp C.Bedeutung
cverkohlenEine Python-Zeichenfolge der Länge 1 wird zu einem C-Zeichen.
ddoppeltEin Python-Float wird zu einem C-Double.
fschwebenEin Python-Float wird zu einem C-Float.
ichintEin Python-Int wird zu einem C-Int.
llangeEin Python-Int wird zu einem C-Long.
L.lang LangEin Python-Int wird zu einem C-Long-Long
ÖPyObject *Ruft einen nicht NULL ausgeliehenen Verweis auf das Python-Argument ab.
sverkohlen*Python-String ohne eingebettete Nullen in C char *.
s #char * + intBeliebige Python-Zeichenfolge mit C-Adresse und Länge.
t #char * + intSchreibgeschützter Einzelsegmentpuffer auf C-Adresse und Länge.
uPy_UNICODE *Python Unicode ohne eingebettete Nullen für C.
u #Py_UNICODE * + intBeliebige Python Unicode C-Adresse und -Länge.
w #char * + intLesen / Schreiben eines Einzelsegmentpuffers auf C-Adresse und Länge.
zverkohlen*Akzeptiert wie s auch None (setzt C char * auf NULL).
z #char * + intAkzeptiert 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 würde mithilfe einer Liste in Python erfasst.

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 zu einem Zeichenfolgenformat zusammengefügt werden.

CodeTyp C.Bedeutung
cverkohlenAC char wird zu einer Python-Zeichenfolge mit der Länge 1.
ddoppeltAC Double wird zu einem Python-Float.
fschwebenAC float wird zu einem Python float.
ichintAC int wird zu Python int.
llangeAC 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
sverkohlen*C 0-terminiertes Zeichen * für Python-Zeichenfolge oder NULL für Keine.
s #char * + intC char * und Länge zu Python-String oder NULL zu None.
uPy_UNICODE *C-weite, nullterminierte Zeichenfolge für Python Unicode oder NULL für Keine.
u #Py_UNICODE * + intC-weite Zeichenfolge und Länge zu Python Unicode oder NULL zu Keine.
w #char * + intLesen / Schreiben eines Einzelsegmentpuffers auf C-Adresse und Länge.
zverkohlen*Akzeptiert wie s auch None (setzt C char * auf NULL).
z #char * + intAkzeptiert 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.