Construire un module d’extension C Python – Real Python

By | octobre 7, 2019

trouver un expert Python

Vous pouvez étendre les fonctionnalités de Python de plusieurs manières. L'une d'elles est d'écrire votre module Python en C ou C ++. Ce processus peut entraîner de meilleures performances et un meilleur accès aux fonctions de la bibliothèque C et aux appels système. Dans ce didacticiel, vous découvrirez comment utiliser l'API Python pour écrire des modules d'extension Python C.

Vous allez apprendre à:

  • Appeler des fonctions C depuis Python
  • Passer les arguments de Python à C et les analyser en conséquence
  • Générez des exceptions à partir du code C et créez des exceptions Python personnalisées en C
  • Définir des constantes globales en C et les rendre accessibles en Python
  • Testez, empaquetez et distribuez votre module d'extension Python C

Extension de votre programme Python

L’une des fonctionnalités moins connues et pourtant incroyablement puissantes de Python est sa capacité à appeler des fonctions et des bibliothèques définies dans langages compilés tels que C ou C ++. Cela vous permet d’étendre les fonctionnalités de votre programme au-delà des fonctionnalités intégrées de Python.

Vous pouvez choisir parmi de nombreuses langues pour étendre les fonctionnalités de Python. Alors, pourquoi devriez-vous utiliser C? Voici quelques raisons pour lesquelles vous pourriez décider de créer un module d’extension Python C:

  1. Pour implémenter de nouveaux types d'objet intégrés: Il est possible d’écrire une classe Python en C, puis d’instancier et d’étendre cette classe à partir de Python lui-même. Cela peut être motivé par de nombreuses raisons, mais le plus souvent, ce sont les performances qui incitent les développeurs à se tourner vers C. Une telle situation est rare, mais il est bon de savoir dans quelle mesure Python peut être étendu.

  2. Pour appeler les fonctions de la bibliothèque C et les appels système: De nombreux langages de programmation fournissent des interfaces avec les appels système les plus couramment utilisés. Cependant, il peut y avoir d’autres appels système moins utilisés qui ne sont accessibles que par C. os module en Python est un exemple.

Cette liste n'est pas exhaustive, mais elle vous donne l'essentiel de ce qui peut être fait pour étendre Python en utilisant C ou tout autre langage.

Pour écrire des modules Python en C, vous devez utiliser l'API Python, qui définit les différentes fonctions, macros et variables permettant à l'interpréteur Python d'appeler votre code C. Tous ces outils et d’autres sont collectivement regroupés dans le Python.h En tête de fichier.

Ecriture d'une interface Python en C

Dans ce tutoriel, vous allez écrire un petit wrapper pour un Fonction de bibliothèque C, que vous invoquerez ensuite à partir de Python. Implémenter vous-même un wrapper vous donnera une meilleure idée de quand et comment utiliser C pour étendre votre module Python.

Compréhension fputs ()

fputs () est la fonction de bibliothèque C que vous allez encapsuler:

int fputs(const carboniser *, FICHIER *)

Cette fonction prend deux arguments:

  1. const char * est un tableau de caractères.
  2. FICHIER * est un pointeur de flux de fichiers.

fputs () écrit le tableau de caractères dans le fichier spécifié par le flux de fichiers et renvoie une valeur non négative. Si l'opération réussit, cette valeur indiquera le nombre d'octets écrits dans le fichier. S'il y a une erreur, alors il retourne EOF. Vous pouvez en savoir plus sur cette fonction de la bibliothèque C et ses autres variantes dans la page de manuel.

Ecriture de la fonction C pour fputs ()

Ceci est un programme C de base qui utilise fputs () écrire une chaîne dans un flux de fichiers:

#comprendre 
#comprendre 
#comprendre 

int principale() 
    FICHIER *fp = fopen("write.txt", "w")
    fputs("Véritable Python!", fp)
    fermer(fp)
    revenir 1;

Cet extrait de code peut être résumé comme suit:

  1. Ouvert le fichier write.txt.
  2. Écrire la ficelle "Véritable Python!" au fichier.

Dans la section suivante, vous allez écrire un wrapper pour cette fonction C.

Emballage fputs ()

Cela peut sembler un peu bizarre de voir le code complet avant d’expliquer son fonctionnement. Cependant, prendre un moment pour inspecter le produit final complétera votre compréhension dans les sections suivantes. Le bloc de code ci-dessous montre la version finale encapsulée de votre code C:

    1 statique PyObject *method_fputs(PyObject *soi, PyObject *args) 
    2     carboniser *str, *nom de fichier = NUL;
    3     int bytes_copied = -1;
    4 
    5     / * Analyser les arguments * /
    6     si(!PyArg_ParseTuple(args, "ss", Etstr, Etnom de fichier)) 
    sept         revenir NUL;
    8     
    9 
dix     FICHIER *fp = fopen(nom de fichier, "w")
11     bytes_copied = fputs(str, fp)
12     fermer(fp)
13 
14     revenir PyLong_FromLong(bytes_copied)
15 

Cet extrait de code fait référence à trois structures d'objet:

  1. PyObject
  2. PyArg_ParseTuple ()
  3. PyLong_FromLong ()

Ceux-ci sont utilisés pour la définition du type de données pour le langage Python. Vous allez passer par chacun d’eux maintenant.

PyObject

PyObject est une structure d'objet que vous utilisez pour définir les types d'objet pour Python. Tous les objets Python partagent un petit nombre de champs définis à l'aide du PyObject structure. Tous les autres types d'objets sont des extensions de ce type.

PyObject indique à l'interpréteur Python de traiter un pointeur sur un objet en tant qu'objet. Par exemple, définir le type de retour de la fonction ci-dessus comme PyObject définit les champs communs requis par l'interpréteur Python pour le reconnaître en tant que type Python valide.

Jetez un autre regard sur les premières lignes de votre code C:

    1 statique PyObject *method_fputs(PyObject *soi, PyObject *args) {
    2     carboniser *str, *nom de fichier = NUL;
    3     int bytes_copied = -1;
    4 
    5     / * Snip * /

À la ligne 2, vous déclarez les types d'arguments que vous souhaitez recevoir à partir de votre code Python:

  1. char * str est la chaîne que vous voulez écrire dans le flux de fichiers.
  2. char * nom de fichier est le nom du fichier à écrire.

PyArg_ParseTuple ()

PyArg_ParseTuple () analyse les arguments que vous recevrez de votre programme Python en variables locales:

    1 statique PyObject *method_fputs(PyObject *soi, PyObject *args) {
    2     carboniser *str, *nom de fichier = NUL;
    3     int bytes_copied = -1;
    4 
    5     / * Analyser les arguments * /
    6     si(!PyArg_ParseTuple(args, "ss", Etstr, Etnom de fichier)) 
    sept         revenir NUL;
    8     
    9 
dix     / * Snip * /

Si vous regardez la ligne 6, alors vous verrez que PyArg_ParseTuple () prend les arguments suivants:

  • args sont de type PyObject.

  • "ss" est le spécificateur de format qui spécifie le type de données des arguments à analyser. (Vous pouvez consulter la documentation officielle pour une référence complète.)

  • & str et &nom de fichier sont des pointeurs sur des variables locales auxquelles les valeurs analysées seront attribuées.

PyArg_ParseTuple () évalue à faux en cas d'échec. Si cela échoue, alors la fonction retournera NUL et ne pas continuer plus loin.

fputs ()

Comme vous l’avez déjà vu, fputs () prend deux arguments, dont l’un est le FICHIER * objet. Comme vous ne pouvez pas analyser un fichier Python textIOwrapper en utilisant l’API Python en C, vous devrez utiliser une solution de contournement:

    1 statique PyObject *method_fputs(PyObject *soi, PyObject *args) 
    2     carboniser *str, *nom de fichier = NUL;
    3     int bytes_copied = -1;
    4 
    5     / * Analyser les arguments * /
    6     si(!PyArg_ParseTuple(args, "ss", Etstr, Etnom de fichier)) 
    sept         revenir NUL;
    8     
    9 
dix     FICHIER *fp = fopen(nom de fichier, "w")
11     bytes_copied = fputs(str, fp)
12     fermer(fp)
13 
14     revenir PyLong_FromLong(bytes_copied)
15 

Voici le détail de ce que fait ce code:

  • À la ligne 10, vous transmettez le nom du fichier que vous utiliserez pour créer un FICHIER * objet et le transmettre à la fonction.
  • À la ligne 11, tu appelles fputs () avec les arguments suivants:
    • str est la chaîne que vous voulez écrire dans le fichier.
    • fp est le FICHIER * objet que vous avez défini à la ligne 10.

Vous stockez ensuite la valeur de retour de fputs () dans bytes_copied. Cette variable entière sera renvoyée à la fputs () invocation dans l'interpréteur Python.

PyLong_FromLong (bytes_copied)

PyLong_FromLong () renvoie un PyLongObject, qui représente un objet entier en Python. Vous pouvez le trouver à la toute fin de votre code C:

    1 statique PyObject *method_fputs(PyObject *soi, PyObject *args) 
    2     carboniser *str, *nom de fichier = NUL;
    3     int bytes_copied = -1;
    4 
    5     / * Analyser les arguments * /
    6     si(!PyArg_ParseTuple(args, "ss", Etstr, Etnom de fichier)) 
    sept         revenir NUL;
    8     
    9 
dix     FICHIER *fp = fopen(nom de fichier, "w")
11     bytes_copied = fputs(str, fp)
12     fermer(fp)
13 
14     revenir PyLong_FromLong(bytes_copied)
15 

La ligne 14 génère un PyLongObject pour bytes_copied, la variable à renvoyer lorsque la fonction est appelée en Python. Vous devez retourner un PyObject * depuis votre module d’extension Python C vers l’interpréteur Python.

Écrire la fonction init

Vous avez écrit le code qui constitue la fonctionnalité principale de votre module d’extension Python C. Cependant, il reste quelques fonctions supplémentaires nécessaires pour que votre module soit opérationnel. Vous devrez écrire les définitions de votre module et des méthodes qu’il contient, comme ceci:

statique PyMethodDef Méthodes Fputs[] = 
    "fputs", method_fputs, METH_VARARGS, "Interface Python pour la fonction de la bibliothèque C de fputs",
    NUL, NUL, 0, NUL
;


statique struct PyModuleDef fputsmodule = 
    PyModuleDef_HEAD_INIT,
    "fputs",
    "Interface Python pour la fonction de la bibliothèque fputs C",
    -1,
    Méthodes Fputs
;

Ces fonctions incluent des méta-informations sur votre module qui seront utilisées par l'interpréteur Python. Passons en revue chacune des structures ci-dessus pour voir comment elles fonctionnent.

PyMethodDef

Pour appeler les méthodes définies dans votre module, vous devez d’abord en informer l’interpréteur Python. Pour ce faire, vous pouvez utiliser PyMethodDef. Il s'agit d'une structure à 4 membres représentant une seule méthode dans votre module.

Idéalement, votre module d’extension Python C contiendra plus d’une méthode que vous souhaitez appeler à partir de l’interpréteur Python. C’est pourquoi vous devez définir un tableau de PyMethodDef structs:

statique PyMethodDef Méthodes Fputs[] = 
    "fputs", method_fputs, METH_VARARGS, "Interface Python pour la fonction de bibliothèque fputs C",
    NUL, NUL, 0, NUL
;

Chaque membre individuel de la structure détient les informations suivantes:

  • "fputs" est le nom que l'utilisateur écrirait pour appeler cette fonction particulière.

  • method_fputs est le nom de la fonction C à appeler.

  • METH_VARARGS est un drapeau qui indique à l'interprète que la fonction acceptera deux arguments de type PyObject *:

    1. soi est l'objet module.
    2. args est un tuple contenant les arguments réels de votre fonction. Comme expliqué précédemment, ces arguments sont décompressés en utilisant PyArg_ParseTuple ().
  • La dernière chaîne est une valeur pour représenter la méthode docstring.

PyModuleDef

Tout comme PyMethodDef contient des informations sur les méthodes de votre module d’extension Python C, le PyModuleDef struct contient des informations sur votre module lui-même. Ce n’est pas un tableau de structures, mais plutôt une structure unique utilisée pour la définition de module:

statique struct PyModuleDef fputsmodule = 
    PyModuleDef_HEAD_INIT,
    "fputs",
    "Interface Python pour la fonction de la bibliothèque fputs C",
    -1,
    Méthodes Fputs
;

Il y a un total de 9 membres dans cette structure, mais tous ne sont pas obligatoires. Dans le bloc de code ci-dessus, vous initialisez les cinq suivants:

  1. PyModuleDef_HEAD_INIT est un membre de type PyModuleDef_Base, qui est conseillé d'avoir juste cette valeur.

  2. "fputs" est le nom de votre module d'extension Python C.

  3. La ficelle est la valeur qui représente votre docstring de module. Vous pouvez utiliser NUL n’avoir aucune docstring, ou vous pouvez spécifier un docstring en passant un const char * comme indiqué dans l'extrait ci-dessus. Il est de type Py_ssize_t. Vous pouvez aussi utiliser PyDoc_STRVAR () définir une docstring pour votre module.

  4. -1 est la quantité de mémoire nécessaire pour stocker l'état de votre programme. C’est utile lorsque votre module est utilisé dans plusieurs sous-interprètes et peut avoir les valeurs suivantes:

    • Une valeur négative indique que ce module ne prend pas en charge les sous-interprètes.
    • Une valeur non négative active la réinitialisation de votre module. Il spécifie également les besoins en mémoire de votre module à allouer sur chaque session de sous-interprète.
  5. Méthodes Fputs est la référence à votre table de méthodes. Ceci est le tableau de PyMethodDef struct que vous avez défini précédemment.

Pour plus d'informations, consultez la documentation officielle de Python sur PyModuleDef.

PyMODINIT_FUNC

Maintenant que vous avez défini votre module d’extension Python C et vos structures de méthodes, il est temps de les utiliser. Lorsqu'un programme Python importe votre module pour la première fois, il appelle PyInit_fputs ():

PyMODINIT_FUNC PyInit_fputs(vide) 
    revenir PyModule_Create(Etfputsmodule)

PyMODINIT_FUNC fait 3 choses implicitement quand il est indiqué comme type de retour de fonction:

  1. Il définit implicitement le type de retour de la fonction comme PyObject *.
  2. Il déclare tous les liens spéciaux.
  3. Il déclare la fonction en tant que «C» externe. Si vous utilisez C ++, il indique au compilateur C ++ de ne pas manipuler les noms avec des symboles.

PyModule_Create () renverra un nouvel objet module de type PyObject *. Pour l’argument, vous passerez l’adresse de la structure de méthode que vous avez déjà définie précédemment, fputsmodule.

Mettre tous ensemble

Maintenant que vous avez écrit les parties nécessaires de votre module d’extension Python C, prenons un peu de recul pour voir comment tout cela s’agence. Le diagramme suivant montre les composants de votre module et leur interaction avec l'interpréteur Python:

Communication API Python C

Lorsque vous importez votre module d’extension Python C, PyInit_fputs () est la première méthode à être invoquée. Cependant, avant qu’une référence ne soit renvoyée à l’interpréteur Python, la fonction appelle ultérieurement PyModule_Create (). Cela initialisera les structures PyModuleDef et PyMethodDef, qui contiennent des méta-informations sur votre module. Il est logique de les avoir prêts car vous les utiliserez dans votre fonction init.

Une fois cette opération terminée, une référence à l'objet module est finalement renvoyée à l'interpréteur Python. Le diagramme suivant montre le flux interne de votre module:

API du module API C Python

L'objet module renvoyé par PyModule_Create () a une référence à la structure du module PyModuleDef, qui à son tour fait référence à la table de méthodes PyMethodDef. Lorsque vous appelez une méthode définie dans votre module d'extension Python C, l'interpréteur Python utilise l'objet module et toutes les références qu'il transporte pour exécuter la méthode spécifique. (Bien que ce ne soit pas exactement la façon dont l’interprète Python gère les choses sous le capot, cela vous donnera une idée de la façon dont cela fonctionne.)

De même, vous pouvez accéder à diverses autres méthodes et propriétés de votre module, telles que la docstring du module ou la méthode docstring. Ceux-ci sont définis à l'intérieur de leurs structures respectives.

Maintenant, vous avez une idée de ce qui se passe lorsque vous appelez fputs () de l'interpréteur Python. L'interprète utilise votre objet module ainsi que les références de module et de méthode pour appeler la méthode. Enfin, voyons comment l’interprète gère l’exécution réelle de votre module d’extension Python C:

<img class = "d-block mx-auto img-fluid" src = "https://files.realpython.com/media/extending_python_high_level_flow.09d2f884e3ad.png" width = "882" height = "180" height = "180" srcset = "https : /robocr Le meilleur des copains est API C Python fputs Flux de fonction »/>

Une fois que method_fputs () est appelé, le programme exécute les étapes suivantes:

  1. Analyser les arguments que vous avez passés de l'interpréteur Python avec PyArg_ParseTuple ()
  2. Passer ces arguments à fputs (), la fonction de bibliothèque C qui constitue le noeud de votre module
  3. Utilisation PyLong_FromLong renvoyer la valeur de fputs ()

Pour voir ces mêmes étapes dans le code, jetez un oeil à method_fputs () encore:

    1 statique PyObject *method_fputs(PyObject *soi, PyObject *args) 
    2     carboniser *str, *nom de fichier = NUL;
    3     int bytes_copied = -1;
    4 
    5     / * Analyser les arguments * /
    6     si(!PyArg_ParseTuple(args, "ss", Etstr, Etnom de fichier)) 
    sept         revenir NUL;
    8     
    9 
dix     FICHIER *fp = fopen(nom de fichier, "w")
11     bytes_copied = fputs(str, fp)
12     fermer(fp)
13 
14     revenir PyLong_FromLong(bytes_copied)
15 

Pour récapituler, votre méthode analysera les arguments transmis à votre module et les enverra à fputs ()et retourne les résultats.

Emballage de votre module d’extension Python C

Avant de pouvoir importer votre nouveau module, vous devez d’abord le construire. Vous pouvez le faire en utilisant le paquet Python distutils.

Vous aurez besoin d’un fichier appelé setup.py installer votre application. Pour ce tutoriel, vous allez vous concentrer sur la partie spécifique au module d’extension Python C. Pour une introduction complète, consultez Comment publier un paquet Python Open Source dans PyPI.

Un minimum setup.py Le fichier de votre module devrait ressembler à ceci:

de distutils.core importation installer, Extension

def principale():
    installer(Nom="fputs",
          version="1.0.0",
          la description="Interface Python pour la fonction de la bibliothèque fputs C",
          auteur="",
          author_email="votre_email@gmail.com",
          ext_modules=[[[[Extension("fputs", [[[["fputsmodule.c"])])

si __Nom__ == "__principale__":
    principale()

Le bloc de code ci-dessus montre les arguments standard transmis à installer(). Regardez de plus près le dernier argument de position, ext_modules. Cela prend une liste d'objets de la Les extensions classe. Un objet du Les extensions La classe décrit un seul module d'extension C ou C ++ dans un script d'installation. Ici, vous transmettez deux arguments de mots clés à son constructeur, à savoir:

  • Nom est le nom du module.
  • [filename] est une liste de chemins d'accès aux fichiers avec le code source, par rapport au script d'installation.

Construire votre module

Maintenant que vous avez votre setup.py fichier, vous pouvez l’utiliser pour construire votre module d’extension Python C. Il est vivement conseillé d’utiliser un environnement virtuel pour éviter les conflits avec votre environnement Python.

Naviguez jusqu'au répertoire contenant setup.py et lancez la commande suivante:

$ Installation de python3.py

Cette commande compilera et installera votre module d’extension Python C dans le répertoire en cours. S'il y a des erreurs ou des avertissements, votre programme les jettera maintenant. Assurez-vous de les corriger avant d'essayer d'importer votre module.

Par défaut, l'interpréteur Python utilise bruit pour compiler le code C. Si vous voulez utiliser gcc ou tout autre compilateur C pour le travail, vous devez alors définir la CC variable d’environnement en conséquence, soit dans le script d’installation, soit directement sur la ligne de commande. Par exemple, vous pouvez indiquer à l'interpréteur Python d'utiliser gcc pour compiler et construire votre module de cette façon:

$ CC=gcc python3 setup.py installer

Cependant, l’interprète Python aura automatiquement recours à gcc si bruit n'est pas disponible.

Lancer votre module

Maintenant que tout est en place, il est temps de voir votre module en action! Une fois la construction réussie, lancez l’interprète pour tester l’exécution de votre module d’extension Python C:

>>>

>>> importation fputs
>>> fputs.__doc__
'Interface Python pour la fonction de la bibliothèque fputs C'
>>> fputs.__Nom__
'fputs'
>>> # Ecrire dans un fichier vide nommé `write.txt`
>>> fputs.fputs("Véritable Python!", "write.txt")
13
>>> avec ouvert("write.txt", "r") comme F:
>>>     impression(F.lis())
'Real Python!'

Votre fonction fonctionne comme prévu! Tu passes une ficelle "Véritable Python!" et un fichier pour écrire cette chaîne, write.txt. L'appel à fputs () renvoie le nombre d'octets écrits dans le fichier. Vous pouvez le vérifier en imprimant le contenu du fichier.

Rappelez-vous aussi comment vous avez passé certains arguments au PyModuleDef et PyMethodDef structures. Vous pouvez voir dans cette sortie que Python a utilisé ces structures pour assigner des éléments tels que le nom de la fonction et la docstring.

Avec cela, vous avez une version de base de votre module prête, mais il y a beaucoup plus que vous pouvez faire! Vous pouvez améliorer votre module en ajoutant des éléments tels que des exceptions et des constantes personnalisées.

Relever des exceptions

Les exceptions Python sont très différentes des exceptions C ++. Si vous souhaitez générer des exceptions Python à partir de votre module d'extension C, vous pouvez utiliser l'API Python pour le faire. Certaines des fonctions fournies par l'API Python pour la levée des exceptions sont les suivantes:

Une fonction La description
PyErr_SetString (type PyObject *,
const char * message)
Prend deux arguments: a PyObject * type argument spécifiant le type d'exception et message personnalisé à afficher à l'utilisateur
PyErr_Format (type PyObject *,
format const char *)
Prend deux arguments: a PyObject * type argument spécifiant le type d'exception et message personnalisé formaté à afficher à l'utilisateur
PyErr_SetObject (type PyObject *,
PyObject * valeur)
Prend deux arguments, tous deux de type PyObject *: le premier spécifie le type d'exception et le second définit un objet Python arbitraire comme valeur d'exception

Vous pouvez utiliser n'importe lequel de ces éléments pour générer une exception. Cependant, lequel utiliser et quand dépend entièrement de vos besoins. L’API Python a toutes les exceptions standard prédéfinies en tant que PyObject les types.

Relever des exceptions du code C

Bien que vous ne puissiez pas générer d’exceptions en C, l’API Python vous permettra de générer des exceptions à partir de votre module d’extension Python C. Essayons cette fonctionnalité en ajoutant PyErr_SetString () à votre code. Cela lève une exception lorsque la longueur de la chaîne à écrire est inférieure à 10 caractères:

    1 statique PyObject *method_fputs(PyObject *soi, PyObject *args) 
    2     carboniser *str, *nom de fichier = NUL;
    3     int bytes_copied = -1;
    4 
    5     / * Analyser les arguments * /
    6     si(!PyArg_ParseTuple(args, "ss", Etstr, Etfd)) 
    sept         revenir NUL;
    8     
    9 
dix     si (Strlen(str) < dix) 
11         PyErr_SetString(PyExc_ValueError, "La longueur de la chaîne doit être supérieure à 10")
12         revenir NUL;
13     
14 
15     fp = fopen(nom de fichier, "w")
16     bytes_copied = fputs(str, fp)
17     fermer(fp)
18 
19     revenir PyLong_FromLong(bytes_copied)
20 

Ici, vous vérifiez la longueur de la chaîne d'entrée immédiatement après l'analyse des arguments et avant l'appel. fputs (). Si la chaîne passée par l'utilisateur est inférieure à 10 caractères, votre programme lève un ValueError avec un message personnalisé. L'exécution du programme s'arrête dès que l'exception se produit.

Notez comment method_fputs () résultats NUL après avoir levé l'exception. En effet, chaque fois que vous déclenchez une exception en utilisant Année _ * (), il définit automatiquement une entrée interne dans la table des exceptions et la renvoie. La fonction appelante n’est pas obligée de redéfinir ultérieurement l’entrée. Pour cette raison, la fonction appelante renvoie une valeur indiquant un échec, généralement NUL ou -1. (Cela devrait aussi expliquer pourquoi il était nécessaire de retourner NUL quand vous analysez des arguments dans method_fputs () en utilisant PyArg_ParseTuple ().)

Relever des exceptions personnalisées

Vous pouvez également générer des exceptions personnalisées dans votre module d’extension Python C. Cependant, les choses sont un peu différentes. Auparavant, dans PyMODINIT_FUNC, vous retourniez simplement l'instance renvoyée par PyModule_Create et appeler un jour. Mais pour que votre exception personnalisée soit accessible à l'utilisateur de votre module, vous devez ajouter votre exception personnalisée à votre instance de module avant de la renvoyer:

statique PyObject *StringTooShortError = NUL;

PyMODINIT_FUNC PyInit_fputs(vide) 
    / * Attribuer une valeur de module * /
    PyObject *module = PyModule_Create(Etfputsmodule)

    / * Initialiser nouvel objet exception * /
    StringTooShortError = PyErr_NewException("fputs.StringTooShortError", NUL, NUL)

    / * Ajouter un objet exception à votre module * /
    PyModule_AddObject(module, "StringTooShortError", StringTooShortError)

    revenir module;

Comme précédemment, vous commencez par créer un objet module. Ensuite, vous créez un nouvel objet exception à l'aide de PyErr_NewException. Cela prend une chaîne de la forme module.nom de classe en tant que nom de la classe d'exception que vous souhaitez créer. Choisissez quelque chose de descriptif pour permettre à l'utilisateur d'interpréter plus facilement ce qui a mal tourné.

Ensuite, vous ajoutez ceci à votre objet module en utilisant PyModule_AddObject. Ceci prend votre objet module, le nom du nouvel objet ajouté et l'objet exception personnalisé lui-même en tant qu'arguments. Enfin, vous retournez votre objet module.

Maintenant que vous avez défini une exception personnalisée à lever par votre module, vous devez mettre à jour method_fputs () afin qu'il soulève l'exception appropriée:

    1 statique PyObject *method_fputs(PyObject *soi, PyObject *args) 
    2     carboniser *str, *nom de fichier = NUL;
    3     int bytes_copied = -1;
    4 
    5     / * Analyser les arguments * /
    6     si(!PyArg_ParseTuple(args, "ss", Etstr, Etfd)) 
    sept         revenir NUL;
    8     
    9 
dix     si (Strlen(str) < dix) 
11         / * Passing exception personnalisée * /
12         PyErr_SetString(StringTooShortError, "La longueur de la chaîne doit être supérieure à 10")
13         revenir NUL;
14     
15 
16     fp = fopen(nom de fichier, "w")
17     bytes_copied = fputs(str, fp)
18     fermer(fp)
19 
20     revenir PyLong_FromLong(bytes_copied)
21 

Après avoir créé le module avec les nouvelles modifications, vous pouvez vérifier que votre exception personnalisée fonctionne comme prévu en essayant d'écrire une chaîne de moins de 10 caractères:

>>>

>>> importation fputs
>>> # Exception personnalisée
>>> fputs.fputs("RP!", fp.fileno())
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
fputs.StringTooShortError: La longueur de la chaîne doit être supérieure à 10

Lorsque vous essayez d'écrire une chaîne de moins de 10 caractères, votre exception personnalisée est générée avec un message expliquant ce qui s'est mal passé.

Définir des constantes

Dans certains cas, vous souhaiterez utiliser ou définir des constantes dans votre module d’extension Python C. Ceci est assez similaire à la façon dont vous avez défini les exceptions personnalisées dans la section précédente. Vous pouvez définir une nouvelle constante et l'ajouter à votre instance de module à l'aide de PyModule_AddIntConstant ():

PyMODINIT_FUNC PyInit_fputs(vide) 
    / * Attribuer une valeur de module * /
    PyObject *module = PyModule_Create(Etfputsmodule)

    / * Ajouter une constante int par nom * /
    PyModule_AddIntConstant(module, "FPUTS_FLAG", 64)

    / * Définir une macro int * /
    #define FPUTS_MACRO 256

    / * Ajouter une macro au module * /
    PyModule_AddIntMacro(module, FPUTS_MACRO)

    revenir module;

Cette fonction de l'API Python utilise les arguments suivants:

  • le exemple de votre module
  • le Nom de la constante
  • le valeur de la constante

Vous pouvez faire la même chose pour les macros en utilisant PyModule_AddIntMacro ():

PyMODINIT_FUNC PyInit_fputs(vide) 
    / * Attribuer une valeur de module * /
    PyObject *module = PyModule_Create(Etfputsmodule)

    / * Ajouter une constante int par nom * /
    PyModule_AddIntConstant(module, "FPUTS_FLAG", 64)

    / * Définir une macro int * /
    #define FPUTS_MACRO 256

    / * Ajouter une macro au module * /
    PyModule_AddIntMacro(module, FPUTS_MACRO)

    revenir module;

Cette fonction prend les arguments suivants:

  • le exemple de votre module
  • le Nom de la macro qui a déjà été définie

Ouvrez l'interpréteur Python pour voir si vos constantes et vos macros fonctionnent comme prévu:

>>>

>>> importation fputs
>>> # Constantes
>>> fputs.FPUTS_FLAG
64
>>> fputs.FPUTS_MACRO
256

Ici, vous pouvez voir que les constantes sont accessibles depuis l’interpréteur Python.

Tester votre module

Vous pouvez tester votre module d’extension Python C comme vous le feriez avec tout autre module Python. Ceci peut être démontré en écrivant une petite fonction de test pour pytest:

importation fputs

def test_copy_data():
    content_to_copy = "Véritable Python!"
    bytes_copied = fputs.fputs(content_to_copy, 'test_write.txt')

    avec ouvert('test_write.txt', 'r') comme F:
        content_copied = F.lis()

    affirmer content_copied == content_to_copy

Dans le script de test ci-dessus, vous utilisez fputs.fputs () écrire la chaîne "Véritable Python!" dans un fichier vide nommé test_write.txt. Ensuite, vous lisez le contenu de ce fichier et utilisez un affirmer déclaration de le comparer à ce que vous aviez écrit à l’origine.

Vous pouvez exécuter cette suite de tests pour vous assurer que votre module fonctionne comme prévu:

$ pytest -q
test_fputs.py                                                 [100%]
1 passé en 0.03 secondes

Pour une introduction plus détaillée, consultez la rubrique Premiers pas avec les tests en Python.

Considérer des alternatives

Dans ce didacticiel, vous avez construit une interface pour une fonction de bibliothèque C afin de comprendre comment écrire des modules d’extension C Python. Cependant, il est parfois nécessaire d'appeler des appels système ou quelques fonctions de la bibliothèque C, et d'éviter ainsi la surcharge liée à l'écriture de deux langues différentes. Dans ces cas, vous pouvez utiliser des bibliothèques Python telles que types ou cffi.

Ceux-ci sont Bibliothèques de fonctions étrangères pour Python qui donnent accès aux fonctions de la bibliothèque C et aux types de données. Bien que la communauté elle-même soit divisée quant à la meilleure bibliothèque, les deux présentent des avantages et des inconvénients. En d'autres termes, l'un ou l'autre constituerait un bon choix pour un projet donné, mais vous devez garder à l'esprit certaines choses lorsque vous devez choisir entre les deux:

  • le types La bibliothèque est incluse dans la bibliothèque standard Python. Ceci est très important si vous voulez éviter les dépendances externes. Il vous permet d’écrire des enveloppes pour d’autres langues en Python.

  • le cffi La bibliothèque n'est pas encore incluse dans la bibliothèque standard. Cela pourrait être un dealbreaker pour votre projet particulier. En général, il s’agit davantage de Pythonic, mais il ne gère pas le prétraitement pour vous.

Pour plus d'informations sur ces bibliothèques, consultez Extending Python With C Libraries et le module «ctypes» et les interfaces Python et C: The CFFI Module.

Conclusion

Dans ce didacticiel, vous avez appris à écrire un Interface python dans le Langage de programmation C en utilisant l'API Python. Vous avez écrit un wrapper Python pour le fputs () Fonction de bibliothèque C. Vous avez également ajouté des exceptions et des constantes personnalisées à votre module avant de le construire et de le tester.

le API Python fournit une foule de fonctionnalités pour écrire des interfaces Python complexes dans le langage de programmation C. Dans le même temps, des bibliothèques telles que cffi ou types peut réduire le temps système nécessaire à l’écriture de modules d’extension Python C. Assurez-vous de peser tous les facteurs avant de prendre une décision!