Python 3 - Programmazione di estensioni con C

Qualsiasi codice che scrivi utilizzando qualsiasi linguaggio compilato come C, C ++ o Java può essere integrato o importato in un altro script Python. Questo codice è considerato come una "estensione".

Un modulo di estensione Python non è altro che una normale libreria C. Sulle macchine Unix, queste librerie di solito terminano con.so(per oggetto condiviso). Sulle macchine Windows, in genere vedi.dll (per libreria collegata dinamicamente).

Prerequisiti per la scrittura di estensioni

Per iniziare a scrivere la tua estensione, avrai bisogno dei file di intestazione Python.

  • Su macchine Unix, questo di solito richiede l'installazione di un pacchetto specifico per sviluppatore come.

  • Gli utenti Windows ottengono queste intestazioni come parte del pacchetto quando usano il programma di installazione binario di Python.

Inoltre, si presume che tu abbia una buona conoscenza di C o C ++ per scrivere qualsiasi estensione Python utilizzando la programmazione C.

Primo sguardo a un'estensione Python

Per il primo sguardo a un modulo di estensione Python, è necessario raggruppare il codice in quattro parti:

  • Il file di intestazione Python.h .

  • Le funzioni C che vuoi esporre come interfaccia dal tuo modulo.

  • Una tabella che mappa i nomi delle tue funzioni come gli sviluppatori Python li vedono come funzioni C all'interno del modulo di estensione.

  • Una funzione di inizializzazione.

Il file di intestazione Python.h

Devi includere il file di intestazione Python.h nel tuo file sorgente C, che ti dà l'accesso all'API Python interna usata per agganciare il tuo modulo all'interprete.

Assicurati di includere Python.h prima di qualsiasi altra intestazione di cui potresti aver bisogno. Devi seguire gli include con le funzioni che vuoi chiamare da Python.

Le funzioni C.

Le firme dell'implementazione C delle tue funzioni assumono sempre una delle tre forme seguenti:

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

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

static PyObject *MyFunctionWithNoArgs( PyObject *self );

Ciascuna delle dichiarazioni precedenti restituisce un oggetto Python. Non esiste una funzione void in Python come in C.Se non vuoi che le tue funzioni restituiscano un valore, restituisci l'equivalente C di PythonNonevalore. Le intestazioni Python definiscono una macro, Py_RETURN_NONE, che fa questo per noi.

I nomi delle tue funzioni C possono essere quelli che ti piacciono poiché non sono mai visti al di fuori del modulo di estensione. Sono definiti come funzione statica .

Le tue funzioni C di solito vengono nominate combinando insieme il modulo Python e i nomi delle funzioni, come mostrato qui -

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

Questa è una funzione Python chiamata func all'interno del modulo del modulo . Inserirai i puntatori alle tue funzioni C nella tabella dei metodi per il modulo che di solito viene dopo nel tuo codice sorgente.

La tabella di mappatura dei metodi

Questa tabella dei metodi è un semplice array di strutture PyMethodDef. Quella struttura assomiglia a questo -

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

Ecco la descrizione dei membri di questa struttura -

  • ml_name - Questo è il nome della funzione che l'interprete Python presenta quando viene utilizzato nei programmi Python.

  • ml_meth - Questo è l'indirizzo di una funzione che ha una delle firme descritte nella sezione precedente.

  • ml_flags - Questo dice all'interprete quale delle tre firme ml_meth sta usando.

    • Questo flag ha solitamente un valore di METH_VARARGS.

    • Questo flag può essere impostato con OR bit per bit con METH_KEYWORDS se si desidera consentire argomenti di parole chiave nella funzione.

    • Può anche avere un valore di METH_NOARGS che indica che non si desidera accettare alcun argomento.

  • ml_doc - Questa è la docstring per la funzione, che potrebbe essere NULL se non hai voglia di scriverne una.

Questa tabella deve essere terminata con una sentinella composta da valori NULL e 0 per i membri appropriati.

Esempio

Per la funzione sopra definita, abbiamo la seguente tabella di mappatura dei metodi:

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

La funzione di inizializzazione

L'ultima parte del modulo di estensione è la funzione di inizializzazione. Questa funzione viene chiamata dall'interprete Python quando il modulo viene caricato. È necessario che la funzione sia denominatainitModule, dove Module è il nome del modulo.

La funzione di inizializzazione deve essere esportata dalla libreria che stai costruendo. Le intestazioni Python definiscono PyMODINIT_FUNC per includere gli incantesimi appropriati affinché ciò avvenga per il particolare ambiente in cui stiamo compilando. Tutto quello che devi fare è usarlo per definire la funzione.

La tua funzione di inizializzazione C ha generalmente la seguente struttura generale:

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

Ecco la descrizione di Py_InitModule3 funzione -

  • func - Questa è la funzione da esportare.

  • module_methods - Questo è il nome della tabella di mappatura definita sopra.

  • docstring - Questo è il commento che vuoi dare nella tua estensione.

Mettendo tutto questo insieme, sembra quanto segue:

#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...");
}

Esempio

Un semplice esempio che fa uso di tutti i concetti di cui sopra:

#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!");
}

Qui la funzione Py_BuildValue viene utilizzata per creare un valore Python. Salva il codice sopra nel file hello.c. Vedremmo come compilare e installare questo modulo da richiamare dallo script Python.

Creazione e installazione di estensioni

Il pacchetto distutils rende molto facile distribuire moduli Python, sia Python puro che moduli di estensione, in modo standard. I moduli sono distribuiti nel formato sorgente, costruiti e installati tramite uno script di installazione solitamente chiamato setup.py as.

Per il modulo precedente, è necessario preparare il seguente script setup.py:

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

Ora, usa il seguente comando, che eseguirà tutte le fasi di compilazione e collegamento necessarie, con i giusti comandi e flag di compilatore e linker, e copia la libreria dinamica risultante in una directory appropriata -

$ python setup.py install

Su sistemi basati su Unix, molto probabilmente sarà necessario eseguire questo comando come root per avere i permessi per scrivere nella directory site-packages. Questo di solito non è un problema su Windows.

Importazione di estensioni

Dopo aver installato le estensioni, sarai in grado di importare e chiamare quell'estensione nel tuo script Python come segue:

Esempio

#!/usr/bin/python3
import helloworld

print helloworld.helloworld()

Produzione

Ciò produrrebbe il seguente risultato:

Hello, Python extensions!!

Passaggio di parametri di funzione

Poiché molto probabilmente vorrai definire funzioni che accettano argomenti, puoi usare una delle altre firme per le tue funzioni C. Ad esempio, la seguente funzione, che accetta un certo numero di parametri, sarebbe definita in questo modo:

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

La tabella dei metodi contenente una voce per la nuova funzione sarebbe simile a questa:

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

È possibile utilizzare la funzione API PyArg_ParseTuple per estrarre gli argomenti dall'unico puntatore PyObject passato alla funzione C.

Il primo argomento di PyArg_ParseTuple è l'argomento args. Questo è l'oggetto verrà parsing . Il secondo argomento è una stringa di formato che descrive gli argomenti come ci si aspetta che appaiano. Ogni argomento è rappresentato da uno o più caratteri nella stringa di formato come segue.

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;
}

Produzione

La compilazione della nuova versione del modulo e l'importazione consente di invocare la nuova funzione con un numero qualsiasi di argomenti di qualsiasi tipo -

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)

Probabilmente puoi inventare ancora più varianti.

La funzione PyArg_ParseTuple

Ecco la firma standard per il file PyArg_ParseTuple funzione -

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

Questa funzione restituisce 0 per gli errori e un valore diverso da 0 per l'esito positivo. Tuple è il PyObject * che era il secondo argomento della funzione C. Qui il formato è una stringa C che descrive argomenti obbligatori e facoltativi.

Di seguito è riportato un elenco di codici di formato per PyArg_ParseTuple funzione -

Codice Tipo C. Senso
c char Una stringa Python di lunghezza 1 diventa un carattere C.
d Doppio Un float Python diventa un C double.
f galleggiante Un float Python diventa un float C.
io int Un Python int diventa un C int.
l lungo Un int Python diventa un C long.
L lungo lungo Un int Python diventa un C long long
O PyObject * Ottiene un riferimento preso in prestito non NULL all'argomento Python.
S char * Stringa Python senza null incorporati in C char *.
S# char * + int Qualsiasi stringa Python in C indirizzo e lunghezza.
t # char * + int Buffer a segmento singolo di sola lettura per indirizzo e lunghezza C.
u Py_UNICODE * Python Unicode senza null incorporati in C.
u # Py_UNICODE * + int Qualsiasi indirizzo e lunghezza Python Unicode C.
w # char * + int Lettura / scrittura del buffer a segmento singolo sull'indirizzo e sulla lunghezza C.
z char * Come s, accetta anche None (imposta C char * su NULL).
z # char * + int Come s #, accetta anche None (imposta il carattere C * su NULL).
(...) come da ... Una sequenza Python viene trattata come un argomento per elemento.
| I seguenti argomenti sono facoltativi.
: Fine del formato, seguito dal nome della funzione per i messaggi di errore.
; Fine del formato, seguito dall'intero testo del messaggio di errore.

Valori restituiti

Py_BuildValue accetta una stringa di formato molto simile a PyArg_ParseTuple . Invece di passare gli indirizzi dei valori che stai costruendo, passi i valori effettivi. Ecco un esempio che mostra come implementare una funzione di aggiunta:

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);
}

Questo è come sarebbe se implementato in Python -

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

È possibile restituire due valori dalla funzione come segue. Questo verrebbe catturato usando un elenco in Python.

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);
}

Questo è come sarebbe se implementato in Python -

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

La funzione Py_BuildValue

Ecco la firma standard per Py_BuildValue funzione -

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

Qui il formato è una stringa C che descrive l'oggetto Python da costruire. I seguenti argomenti di Py_BuildValue sono valori C da cui viene creato il risultato. Il risultato di PyObject * è un nuovo riferimento.

La tabella seguente elenca le stringhe di codice comunemente utilizzate, di cui zero o più sono unite in un formato stringa.

Codice Tipo C. Senso
c char AC char diventa una stringa Python di lunghezza 1.
d Doppio AC double diventa un float Python.
f galleggiante AC float diventa un Python float.
io int AC int diventa un Python int.
l lungo AC long diventa un int Python.
N PyObject * Passa un oggetto Python e ruba un riferimento.
O PyObject * Passa un oggetto Python e lo INCREF normalmente.
O & converti + void * Conversione arbitraria
S char * C 0-terminated char * to Python string, or NULL to None.
S# char * + int Carattere C * e lunghezza alla stringa Python o NULL a Nessuno.
u Py_UNICODE * Stringa C-wide, con terminazione null per Python Unicode o NULL per None.
u # Py_UNICODE * + int Stringa C-wide e lunghezza su Python Unicode o NULL su None.
w # char * + int Lettura / scrittura del buffer a segmento singolo sull'indirizzo e sulla lunghezza C.
z char * Come s, accetta anche None (imposta C char * su NULL).
z # char * + int Come s #, accetta anche None (imposta il carattere C * su NULL).
(...) come da ... Compila la tupla Python da valori C.
[...] come da ... Crea un elenco Python da valori C.
{...} come da ... Costruisce il dizionario Python da valori C, alternando chiavi e valori.

Il codice {...} crea dizionari da un numero pari di valori C, alternativamente chiavi e valori. Ad esempio, Py_BuildValue ("{issi}", 23, "zig", "zag", 42) restituisce un dizionario come {23: 'zig', 'zag': 42} di Python.