Python - Programmation d'extension avec C

Tout code que vous écrivez à l'aide d'un langage compilé tel que C, C ++ ou Java peut être intégré ou importé dans un autre script Python. Ce code est considéré comme une «extension».

Un module d'extension Python n'est rien de plus qu'une bibliothèque C normale. Sur les machines Unix, ces bibliothèques se terminent généralement par.so(pour objet partagé). Sur les machines Windows, vous voyez généralement.dll (pour bibliothèque liée dynamiquement).

Prérequis pour l'écriture d'extensions

Pour commencer à écrire votre extension, vous aurez besoin des fichiers d'en-tête Python.

  • Sur les machines Unix, cela nécessite généralement l'installation d'un package spécifique au développeur tel que python2.5-dev .

  • Les utilisateurs Windows obtiennent ces en-têtes dans le cadre du package lorsqu'ils utilisent le programme d'installation binaire Python.

De plus, il est supposé que vous avez une bonne connaissance de C ou C ++ pour écrire une extension Python à l'aide de la programmation C.

Premier regard sur une extension Python

Pour votre premier aperçu d'un module d'extension Python, vous devez regrouper votre code en quatre parties -

  • Le fichier d'en-tête Python.h .

  • Les fonctions C que vous souhaitez exposer comme interface de votre module.

  • Un tableau mappant les noms de vos fonctions tels que les développeurs Python les voient aux fonctions C à l'intérieur du module d'extension.

  • Une fonction d'initialisation.

Le fichier d'en-tête Python.h

Vous devez inclure le fichier d'en-tête Python.h dans votre fichier source C, ce qui vous donne accès à l'API Python interne utilisée pour connecter votre module à l'interpréteur.

Assurez-vous d'inclure Python.h avant tout autre en-tête dont vous pourriez avoir besoin. Vous devez suivre les inclusions avec les fonctions que vous souhaitez appeler depuis Python.

Les fonctions C

Les signatures de l'implémentation C de vos fonctions prennent toujours l'une des trois formes suivantes -

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

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

static PyObject *MyFunctionWithNoArgs( PyObject *self );

Chacune des déclarations précédentes renvoie un objet Python. Il n'y a pas de fonction void en Python comme en C. Si vous ne voulez pas que vos fonctions retournent une valeur, retournez l'équivalent C de PythonNonevaleur. Les en-têtes Python définissent une macro, Py_RETURN_NONE, qui fait cela pour nous.

Les noms de vos fonctions C peuvent être ce que vous voulez car ils ne sont jamais vus en dehors du module d'extension. Ils sont définis comme une fonction statique .

Vos fonctions C sont généralement nommées en combinant le module Python et les noms de fonction ensemble, comme indiqué ici -

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

Il s'agit d'une fonction Python appelée func à l' intérieur du module module . Vous allez mettre des pointeurs vers vos fonctions C dans la table des méthodes pour le module qui vient généralement après dans votre code source.

Le tableau de mappage des méthodes

Cette table de méthodes est un simple tableau de structures PyMethodDef. Cette structure ressemble à quelque chose comme ça -

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

Voici la description des membres de cette structure -

  • ml_name - C'est le nom de la fonction que l'interpréteur Python présente lorsqu'il est utilisé dans les programmes Python.

  • ml_meth - Il doit s'agir de l'adresse d'une fonction qui possède l'une des signatures décrites dans la section précédente.

  • ml_flags - Ceci indique à l'interpréteur laquelle des trois signatures ml_meth utilise.

    • Cet indicateur a généralement une valeur de METH_VARARGS.

    • Cet indicateur peut être binaire OU avec METH_KEYWORDS si vous souhaitez autoriser les arguments de mot-clé dans votre fonction.

    • Cela peut également avoir une valeur de METH_NOARGS qui indique que vous ne souhaitez accepter aucun argument.

  • ml_doc - Ceci est la docstring de la fonction, qui pourrait être NULL si vous n'avez pas envie d'en écrire une.

Cette table doit être terminée par une sentinelle composée de valeurs NULL et 0 pour les membres appropriés.

Exemple

Pour la fonction définie ci-dessus, nous avons le tableau de mappage de méthode suivant -

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

La fonction d'initialisation

La dernière partie de votre module d'extension est la fonction d'initialisation. Cette fonction est appelée par l'interpréteur Python lorsque le module est chargé. Il est nécessaire que la fonction soit nomméeinitModule, où Module est le nom du module.

La fonction d'initialisation doit être exportée depuis la bibliothèque que vous allez construire. Les en-têtes Python définissent PyMODINIT_FUNC pour inclure les incantations appropriées pour que cela se produise pour l'environnement particulier dans lequel nous compilons. Tout ce que vous avez à faire est de l'utiliser lors de la définition de la fonction.

Votre fonction d'initialisation C a généralement la structure globale suivante -

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

Voici la description de la fonction Py_InitModule3 -

  • func - C'est la fonction à exporter.

  • module_methods - Il s'agit du nom de la table de mappage défini ci-dessus.

  • docstring - C'est le commentaire que vous souhaitez donner dans votre extension.

Mettre tout cela ensemble ressemble à ce qui suit -

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

Exemple

Un exemple simple qui utilise tous les concepts ci-dessus -

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

Ici, la fonction Py_BuildValue est utilisée pour créer une valeur Python. Enregistrez le code ci-dessus dans le fichier hello.c. Nous verrions comment compiler et installer ce module pour être appelé à partir d'un script Python.

Création et installation d'extensions

Le paquet distutils facilite la distribution des modules Python, à la fois en Python pur et en modules d'extension, de manière standard. Les modules sont distribués sous forme source et construits et installés via un script de configuration généralement appelé setup.py comme suit.

Pour le module ci-dessus, vous devez préparer le script setup.py suivant -

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

Maintenant, utilisez la commande suivante, qui effectuerait toutes les étapes de compilation et de liaison nécessaires, avec les bons commandes et indicateurs du compilateur et de l'éditeur de liens, et copie la bibliothèque dynamique résultante dans un répertoire approprié -

$ python setup.py install

Sur les systèmes Unix, vous devrez probablement exécuter cette commande en tant que root pour avoir les autorisations d'écrire dans le répertoire site-packages. Ce n'est généralement pas un problème sous Windows.

Importation d'extensions

Une fois que vous avez installé votre extension, vous pourrez importer et appeler cette extension dans votre script Python comme suit -

#!/usr/bin/python
import helloworld

print helloworld.helloworld()

Cela produirait le résultat suivant -

Hello, Python extensions!!

Passer des paramètres de fonction

Comme vous voudrez probablement définir des fonctions qui acceptent des arguments, vous pouvez utiliser l'une des autres signatures pour vos fonctions C. Par exemple, la fonction suivante, qui accepte un certain nombre de paramètres, serait définie comme ceci -

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

La table de méthode contenant une entrée pour la nouvelle fonction ressemblerait à ceci -

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

Vous pouvez utiliser la fonction API PyArg_ParseTuple pour extraire les arguments du pointeur PyObject transmis à votre fonction C.

Le premier argument de PyArg_ParseTuple est l'argument args. C'est l'objet que vous analyserez . Le deuxième argument est une chaîne de format décrivant les arguments tels que vous vous attendez à ce qu'ils apparaissent. Chaque argument est représenté par un ou plusieurs caractères dans la chaîne de format comme suit.

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

Compiler la nouvelle version de votre module et l'importer vous permet d'appeler la nouvelle fonction avec n'importe quel nombre d'arguments de n'importe quel 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)

Vous pouvez probablement proposer encore plus de variantes.

La fonction PyArg_ParseTuple

Voici la signature standard pour PyArg_ParseTuple fonction -

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

Cette fonction renvoie 0 pour les erreurs et une valeur différente de 0 pour le succès. tuple est le PyObject * qui était le deuxième argument de la fonction C. Ici, le format est une chaîne C qui décrit les arguments obligatoires et facultatifs.

Voici une liste de codes de format pour PyArg_ParseTuple fonction -

Code Type C Sens
c carboniser Une chaîne Python de longueur 1 devient un caractère C.
double Un flotteur Python devient un double C.
F flotte Un flotteur Python devient un flotteur C.
je int Un Python int devient un C int.
l longue Un Python int devient un C long.
L long long Un Python int devient un C long long
O PyObject * Obtient une référence empruntée non NULL à l'argument Python.
s carboniser* Chaîne Python sans NULL incorporé au caractère C *.
s # char * + int Toute chaîne Python à l'adresse et à la longueur C.
t # char * + int Tampon à un seul segment en lecture seule à l'adresse et à la longueur C.
u Py_UNICODE * Python Unicode sans valeurs nulles incorporées à C.
u # Py_UNICODE * + int Toute adresse et longueur Python Unicode C.
w # char * + int Tampon en lecture / écriture à un seul segment à l'adresse et à la longueur C.
z carboniser* Comme s, accepte également None (définit C char * sur NULL).
z # char * + int Comme s #, accepte également None (définit C char * sur NULL).
(...) selon ... Une séquence Python est traitée comme un argument par élément.
|   Les arguments suivants sont facultatifs.
:   Fin du format, suivi du nom de la fonction pour les messages d'erreur.
;   Fin du format, suivi du texte complet du message d'erreur.

Valeurs renvoyées

Py_BuildValue prend dans une chaîne de format un peu comme PyArg_ParseTuple le fait. Au lieu de transmettre les adresses des valeurs que vous créez, vous transmettez les valeurs réelles. Voici un exemple montrant comment implémenter une fonction d'ajout -

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

Voici à quoi cela ressemblerait s'il était implémenté en Python -

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

Vous pouvez renvoyer deux valeurs de votre fonction comme suit, cela serait caupturé à l'aide d'une liste en 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);
}

Voici à quoi cela ressemblerait s'il était implémenté en Python -

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

La fonction Py_BuildValue

Voici la signature standard pour Py_BuildValue fonction -

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

Ici, le format est une chaîne C qui décrit l'objet Python à construire. Les arguments suivants de Py_BuildValue sont des valeurs C à partir desquelles le résultat est construit. Le résultat PyObject * est une nouvelle référence.

Le tableau suivant répertorie les chaînes de code couramment utilisées, dont zéro ou plus sont jointes au format chaîne.

Code Type C Sens
c carboniser AC char devient une chaîne Python de longueur 1.
double AC double devient un flotteur Python.
F flotte AC float devient un float Python.
je int AC int devient un Python int.
l longue AC long devient un int Python.
N PyObject * Passe un objet Python et vole une référence.
O PyObject * Passe un objet Python et l'INCREF comme d'habitude.
O & convertir + annuler * Conversion arbitraire
s carboniser* C 0-terminé char * en chaîne Python, ou NULL en None.
s # char * + int C char * et longueur en chaîne Python, ou NULL en None.
u Py_UNICODE * Chaîne C-wide, terminée par NULL à Python Unicode, ou NULL à None.
u # Py_UNICODE * + int Chaîne C-wide et longueur en Python Unicode, ou NULL en None.
w # char * + int Tampon en lecture / écriture à un seul segment à l'adresse et à la longueur C.
z carboniser* Comme s, accepte également None (définit C char * sur NULL).
z # char * + int Comme s #, accepte également None (définit C char * sur NULL).
(...) selon ... Construit un tuple Python à partir de valeurs C.
[...] selon ... Construit une liste Python à partir de valeurs C.
{...} selon ... Construit un dictionnaire Python à partir de valeurs C, en alternant les clés et les valeurs.

Code {...} construit des dictionnaires à partir d'un nombre pair de valeurs C, alternativement de clés et de valeurs. Par exemple, Py_BuildValue ("{issi}", 23, "zig", "zag", 42) renvoie un dictionnaire comme celui de Python {23: 'zig', 'zag': 42}.