Python Extensieprogrammering met C: Snelle, Native Modules Bouwen
Elke code die u schrijft met een gecompileerde taal zoals C, C++ of Java, kan geïntegreerd of geïmporteerd worden in een ander Python-script. Deze code wordt beschouwd als een "extensie".
Een Python-extensiemodule is niet meer dan een normale C-bibliotheek. Op Unix-machines eindigen deze bibliotheken meestal op .so (voor shared object). Op Windows-machines ziet u doorgaans .dll (voor dynamically linked library).
Vereisten voor het Schrijven van Extensies
Om te beginnen met het schrijven van uw extensie, hebt u de Python-headerbestanden nodig.
Op Unix-machines vereist dit doorgaans het installeren van een voor ontwikkelaars specifiek pakket zoals python2.5-dev.
Windows-gebruikers krijgen deze headers als onderdeel van het pakket wanneer ze de binaire Python-installer gebruiken.
Daarnaast wordt ervan uitgegaan dat u over goede kennis van C of C++ beschikt om een Python-extensie met C-programmering te schrijven.
Eerste Blik op een Python-extensie
Voor uw eerste blik op een Python-extensiemodule, moet u uw code in vier delen groeperen:
Het headerbestand Python.h.
De C-functies die u wilt blootstellen als interface vanuit uw module.
Een tabel die de namen van uw functies, zoals Python-ontwikkelaars ze zien, koppelt aan C-functies binnen de extensiemodule.
Een initialisatiefunctie.
Het Headerbestand Python.h
U moet het Python.h-headerbestand in uw C-bronbestand includen, wat u toegang geeft tot de interne Python-API die gebruikt wordt om uw module in de interpreter te haken.
Zorg ervoor dat u Python.h vóór andere headers includet die u misschien nodig hebt. U moet de includes volgen met de functies die u vanuit Python wilt aanroepen.
De C-Functies
De handtekeningen van de C-implementatie van uw functies hebben altijd een van de volgende drie vormen:
static PyObject *MyFunction(PyObject *self, PyObject *args); static PyObject *MyFunctionWithKeywords(PyObject *self, PyObject *args, PyObject *kw); static PyObject *MyFunctionWithNoArgs(PyObject *self);
Elke van de voorgaande declaraties retourneert een Python-object. Er is zoiets als een void-functie in Python niet zoals in C. Als u niet wilt dat uw functies een waarde retourneren, retourneer dan het C-equivalent van Python's None-waarde. De Python-headers definiëren een macro, Py_RETURN_NONE, die dit voor ons doet.
De namen van uw C-functies kunnen wat u wilt zijn omdat ze nooit buiten de extensiemodule gezien worden. Ze worden gedefinieerd als static functies.
Uw C-functies worden meestal genoemd door de Python-module- en functienamen te combineren, zoals hier getoond:
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Do your stuff here. */
Py_RETURN_NONE;
}
Dit is een Python-functie genaamd func binnen de module module. U zal pointers naar uw C-functies in de methodetabel voor de module plaatsen die meestal als volgende in uw broncode komt.
De Methode-Mappingtabel
Deze methodetabel is een eenvoudige array van PyMethodDef-structuren. Die structuur ziet er ongeveer zo uit:
struct PyMethodDef {
char *ml_name;
PyCFunction ml_meth;
int ml_flags;
char *ml_doc;
};
Beschrijving van de leden van deze structuur:
ml_name - Dit is de naam van de functie zoals de Python-interpreter presenteert wanneer deze in Python-programma's gebruikt wordt.
ml_meth - Dit moet het adres zijn naar een functie die een van de handtekeningen heeft die in de vorige sectie beschreven zijn.
ml_flags - Dit vertelt de interpreter welke van de drie handtekeningen ml_meth gebruikt.
Deze vlag heeft meestal een waarde van METH_VARARGS.
Deze vlag kan bitwise OR'ed worden met METH_KEYWORDS als u keyword-argumenten in uw functie wilt toestaan.
Deze kan ook een waarde van METH_NOARGS hebben die aangeeft dat u geen argumenten wilt accepteren.
ml_doc - Dit is de docstring voor de functie, die NULL kan zijn als u er niet aan wilt schrijven.
Deze tabel moet beëindigd worden met een sentinel die bestaat uit NULL en 0-waarden voor de geschikte leden.
Voorbeeld
Voor de boven gedefinieerde functie hebben we de volgende methodemappingtabel:
static PyMethodDef module_methods[] = {
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ NULL, NULL, 0, NULL }
};
De Initialisatiefunctie
Het laatste deel van uw extensiemodule is de initialisatiefunctie. Deze functie wordt aangeroepen door de Python-interpreter wanneer de module geladen wordt. Het is vereist dat de functie initModule heet, waar Module de naam van de module is.
De initialisatiefunctie moet geëxporteerd worden vanuit de library die u gaat bouwen. De Python-headers definiëren PyMODINIT_FUNC om de juiste toverspreuken voor dat te laten gebeuren voor de specifieke omgeving waarin we compileren. Alles wat u hoeft te doen is het gebruiken bij het definiëren van de functie.
Uw C-initialisatiefunctie heeft doorgaans de volgende algehele structuur:
PyMODINIT_FUNC initModule() {
Py_InitModule3(func, module_methods, "docstring...");
}
Beschrijving van de Py_InitModule3-functie:
func - Dit is de functie die geëxporteerd moet worden.
module_methods - Dit is de naam van de mappingtabel die hierboven gedefinieerd is.
docstring - Dit is de commentaar die u in uw extensie wilt geven.
Alles samen gevoegd ziet er als volgt uit:
#includestatic 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..."); }
Voorbeeld
Een simpel voorbeeld dat al bovenstaande concepten gebruikt:
#includestatic PyObject* helloworld(PyObject* self) { return Py_BuildValue("s", "Hello, Python extensions!!"); } static char helloworld_docs[] = "helloworld( ): Elk bericht dat u wilt plaatsen!!\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 wordt de Py_BuildValue-functie gebruikt om een Python-waarde te bouwen. Sla de bovenstaande code op in hello.c bestand. We zullen zien hoe u deze module compileert en installeert om te worden aangeroepen vanuit een Python-script.
Extensies Bouwen en Installeren
Het distutils-pakket maakt het heel eenvoudig om Python-modules te distribueren, zowel pure Python als extensiemodules, op een standaardmanier. Modules worden in bronvorm gedistribueerd en gebouwd en geïnstalleerd via een setup-script meestal genaamd setup.py als volgt.
Voor de bovenstaande module moet u het volgende setup.py-script voorbereiden:
from distutils.core import setup, Extension
setup(name='helloworld', version='1.0',
ext_modules=[Extension('helloworld', ['hello.c'])])
Gebruik nu het volgende commando, dat alle benodigde compilatie- en linkstappen uitvoert, met de juiste compiler- en linker-commando's en vlaggen, en de resulterende dynamische bibliotheek in een geschikte directory kopieert:
$ python setup.py install
Op Unix-gebaseerde systemen moet u dit commando waarschijnlijk als root uitvoeren om machtigingen te hebben om in de site-packages-directory te schrijven. Dit is meestal geen probleem op Windows.
Extensies Importeren
Eenmaal uw extensie geïnstalleerd, kunt u deze importeren en aanroepen in uw Python-script als volgt:
#!/usr/bin/python import helloworld print helloworld.helloworld()
Dit produceert het volgende resultaat:
Hello, Python extensions!!
Functieparameters Doorgeven
Omdat u waarschijnlijk functies wilt definiëren die argumenten accepteren, kunt u een van de andere handtekeningen voor uw C-functies gebruiken. Bijvoorbeeld, de volgende functie, die een aantal parameters accepteert, zou als volgt gedefinieerd worden:
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Parse args en doe iets interessants hier. */
Py_RETURN_NONE;
}
De methodetabel met een entry voor de nieuwe functie zou er als volgt uitzien:
static PyMethodDef module_methods[] = {
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ "func", module_func, METH_VARARGS, NULL },
{ NULL, NULL, 0, NULL }
};
U kunt de API PyArg_ParseTuple-functie gebruiken om de argumenten uit de ene PyObject-pointer die aan uw C-functie wordt doorgegeven, te extraheren.
Het eerste argument voor PyArg_ParseTuple is het args-argument. Dit is het object dat u gaat parsen. Het tweede argument is een formatstring die de argumenten beschrijft zoals u ze verwacht. Elk argument wordt vertegenwoordigd door een of meer tekens in de formatstring als volgt.
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;
}
De nieuwe versie van uw module compileren en importeren stelt u in staat de nieuwe functie aan te roepen met elk aantal argumenten van elk type:
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)
U kunt waarschijnlijk nog meer variaties bedenken.
De PyArg_ParseTuple-Functie
Hier is de standaard handtekening voor de PyArg_ParseTuple-functie:
int PyArg_ParseTuple(PyObject* tuple, char* format, ...)
Deze functie retourneert 0 voor fouten, en een waarde niet gelijk aan 0 voor succes. tuple is de PyObject* die het tweede argument van de C-functie was. Hier is format een C-string die verplichte en optionele argumenten beschrijft.
Hier is een lijst van formatcodes voor de PyArg_ParseTuple-functie:
| Code | C-type | Betekenis |
|---|---|---|
| c | char | Een Python-string van lengte 1 wordt een C char. |
| d | double | Een Python-float wordt een C double. |
| f | float | Een Python-float wordt een C float. |
| i | int | Een Python-int wordt een C int. |
| l | long | Een Python-int wordt een C long. |
| L | long long | Een Python-int wordt een C long long |
| O | PyObject* | Krijgt niet-NULL geborgde referentie naar Python-argument. |
| s | char* | Python-string zonder ingesloten nulls naar C char*. |
| s# | char*+int | Elke Python-string naar C-adres en lengte. |
| t# | char*+int | Alleen-lezen enkel-segment buffer naar C-adres en lengte. |
| u | Py_UNICODE* | Python Unicode zonder ingesloten nulls naar C. |
| u# | Py_UNICODE*+int | Elke Python Unicode C-adres en lengte. |
| w# | char*+int | Lees/schrijf enkel-segment buffer naar C-adres en lengte. |
| z | char* | Als s, accepteert ook None (stelt C char* in op NULL). |
| z# | char*+int | Als s#, accepteert ook None (stelt C char* in op NULL). |
| (...) | zoals per ... | Een Python-sequentie wordt behandeld als één argument per item. |
| | | De volgende argumenten zijn optioneel. | |
| : | Formateinde, gevolgd door functienaam voor foutmeldingen. | |
| ; | Formateinde, gevolgd door volledige foutmeldingstekst. |
Waarden Teruggeven
Py_BuildValue neemt een formatstring net zoals PyArg_ParseTuple doet. In plaats van de adressen van de waarden die u bouwt door te geven, geeft u de daadwerkelijke waarden door. Hier is een voorbeeld dat laat zien hoe u een add-functie implementeert:
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);
}
Dit is hoe het eruit zou zien als geïmplementeerd in Python:
def add(a, b):
return (a + b)
U kunt twee waarden uit uw functie retourneren als volgt, dit zou in Python worden vastgelegd met een lijst.
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);
}
Dit is hoe het eruit zou zien als geïmplementeerd in Python:
def add_subtract(a, b):
return (a + b, a - b)
De Py_BuildValue-Functie
Hier is de standaard handtekening voor de Py_BuildValue-functie:
PyObject* Py_BuildValue(char* format, ...)
Hier is format een C-string die het Python-object beschrijft dat moet worden gebouwd. De volgende argumenten van Py_BuildValue zijn C-waarden waarvan het resultaat wordt gebouwd. Het PyObject*-resultaat is een nieuwe referentie.
De volgende tabel geeft de veelvoorkomende codestrings, waarvan nul of meer samengevoegd worden in string-formaat.
| Code | C-type | Betekenis |
|---|---|---|
| c | char | Een C char wordt een Python-string van lengte 1. |
| d | double | Een C double wordt een Python-float. |
| f | float | Een C float wordt een Python-float. |
| i | int | Een C int wordt een Python-int. |
| l | long | Een C long wordt een Python-int. |
| N | PyObject* | Geeft een Python-object door en steelt een referentie. |
| O | PyObject* | Geeft een Python-object door en INCREFs het als normaal. |
| O& | convert+void* | Willekeurige conversie |
| s | char* | C 0-getermineerde char* naar Python-string, of NULL naar None. |
| s# | char*+int | C char* en lengte naar Python-string, of NULL naar None. |
| u | Py_UNICODE* | C-brede, null-getermineerde string naar Python Unicode, of NULL naar None. |
| u# | Py_UNICODE*+int | C-brede string en lengte naar Python Unicode, of NULL naar None. |
| w# | char*+int | Lees/schrijf enkel-segment buffer naar C-adres en lengte. |
| z | char* | Als s, accepteert ook None (stelt C char* in op NULL). |
| z# | char*+int | Als s#, accepteert ook None (stelt C char* in op NULL). |
| (...) | zoals per ... | Bouwt Python-tuple van C-waarden. |
| [...] | zoals per ... | Bouwt Python-lijst van C-waarden. |
| {...} | zoals per ... | Bouwt Python-dictionary van C-waarden, afwisselend sleutels en waarden. |
Code {...} bouwt dictionaries van een even aantal C-waarden, afwisselend sleutels en waarden. Bijvoorbeeld, Py_BuildValue("{issi}",23,"zig","zag",42) retourneert een dictionary als Python's {23:'zig','zag':42}.
Python
- Python Object-Oriented Programming beheersen: classes, inheritance, encapsulatie & polymorfisme
- Python print()-functie: Praktische handleiding met voorbeelden
- Python's strip()-methode beheersen: Uitgebreide handleiding & praktische voorbeelden
- Python's str.count() beheersen: Karakters & substrings tellen met voorbeelden
- Python `format()` strings beheersen met duidelijke voorbeelden
- Python's String.find()-methode beheersen: Syntaxis, voorbeelden & alternatieven
- Python lambda-functies beheersen: praktische voorbeelden & best practices
- Python round()-functie uitgelegd met praktische voorbeelden
- Python's map()-functie beheersen: syntaxis, voorbeelden en best practices
- Python timeit() – Uitvoeringstijd meten met praktische voorbeelden
- Python Counter in collections – Efficiënt tellen, updaten en rekenkundige operaties