Appel de C ou C ++ à partir de Python – Real Python

By | mars 2, 2020

Cours Python en ligne

Êtes-vous un développeur Python avec une bibliothèque C ou C ++ que vous souhaitez utiliser à partir de Python? Si oui, alors Liaisons Python vous permet d'appeler des fonctions et de transmettre des données de Python en C ou C ++, vous permettant de profiter des avantages des deux langages. Tout au long de ce didacticiel, vous verrez un aperçu de certains des outils que vous pouvez utiliser pour créer des liaisons Python.

Dans ce didacticiel, vous découvrirez:

  • Pourquoi tu veux appeler C ou C ++ de Python
  • Comment transmettre des données entre C et Python
  • Quoi outils et méthodes peut vous aider à créer des liaisons Python

Ce tutoriel s'adresse aux développeurs Python intermédiaires. Il suppose une connaissance de base de Python et une certaine compréhension des fonctions et des types de données en C ou C ++. Vous pouvez obtenir tous les exemples de code que vous verrez dans ce didacticiel en cliquant sur le lien ci-dessous:

Voyons les liaisons Python!

Présentation des liaisons Python

Avant de plonger Comment pour appeler C depuis Python, il est bon de passer du temps sur Pourquoi. Il existe plusieurs situations où la création de liaisons Python pour appeler une bibliothèque C est une excellente idée:

  1. Vous disposez déjà d'une grande bibliothèque testée et stable écrite en C ++ dont vous aimeriez profiter en Python. Il peut s'agir d'une bibliothèque de communication ou d'une bibliothèque pour parler à un matériel spécifique. Ce qu'il fait n'a pas d'importance.

  2. Vous souhaitez accélérer une section particulière de votre code Python en convertissant une section critique en C. Non seulement C a une vitesse d'exécution plus rapide, mais il vous permet également de vous libérer des limitations de GIL, à condition d'être prudent.

  3. Vous souhaitez utiliser les outils de test Python pour effectuer des tests à grande échelle de leurs systèmes.

Tous ces éléments sont d'excellentes raisons d'apprendre à créer des liaisons Python pour s'interfacer avec votre bibliothèque C.

Commençons!

Mise en forme des types de données

Attendez! Avant de commencer à écrire des liaisons Python, regardez comment Python et C stockent les données et quels types de problèmes cela entraînera. Tout d'abord, définissons triage. Ce concept est défini par Wikipedia comme suit:

Processus de transformation de la représentation en mémoire d'un objet en un format de données adapté au stockage ou à la transmission. (La source)

Pour vos besoins, le marshaling est ce que font les liaisons Python lorsqu'elles préparent des données pour les déplacer de Python vers C ou vice versa. Les liaisons Python doivent effectuer un marshaling car Python et C stockent les données de différentes manières. C stocke les données sous la forme la plus compacte possible en mémoire. Si vous utilisez un uint8_t, il n'utilisera alors que 8 bits de mémoire au total.

En Python, en revanche, tout est un objet. Cela signifie que chaque entier utilise plusieurs octets en mémoire. Le nombre dépendra de la version de Python que vous exécutez, de votre système d'exploitation et d'autres facteurs. Cela signifie que vos liaisons Python devront convertir un Entier C à un Entier Python pour chaque entier passé à travers la frontière.

D'autres types de données ont des relations similaires entre les deux langues. Examinons chacun à son tour:

  • Entiers stocker les numéros de comptage. Python stocke des entiers avec une précision arbitraire, ce qui signifie que vous pouvez stocker de très, très grands nombres. C spécifie les tailles exactes des entiers. Vous devez être conscient de la taille des données lorsque vous vous déplacez entre les langues pour éviter que les valeurs entières Python ne débordent de variables entières C.

  • Nombres à virgule flottante sont des nombres avec une décimale. Python peut stocker des nombres à virgule flottante beaucoup plus grands (et beaucoup plus petits) que C. Cela signifie que vous devrez également faire attention à ces valeurs pour vous assurer qu'elles restent dans la plage.

  • Nombres complexes sont des nombres avec une partie imaginaire. Alors que Python a des nombres complexes intégrés et C a des nombres complexes, il n'y a pas de méthode intégrée pour le marshaling entre eux. Pour rassembler des nombres complexes, vous devez créer un struct ou classe dans le code C pour les gérer.

  • Cordes sont des séquences de caractères. Pour être un type de données aussi courant, les chaînes se révéleront plutôt délicates lorsque vous créez des liaisons Python. Comme avec d'autres types de données, Python et C stockent des chaînes dans des formats assez différents. (Contrairement aux autres types de données, c'est un domaine où C et C ++ diffèrent également, ce qui ajoute au plaisir!) Chacune des solutions que vous examinerez a des méthodes légèrement différentes pour traiter les chaînes.

  • Variables booléennes ne peut avoir que deux valeurs. Puisqu'ils sont pris en charge en C, leur regroupement s'avérera assez simple.

Outre les conversions de types de données, vous devrez penser à d'autres problèmes lors de la création de vos liaisons Python. Continuons à les explorer.

Comprendre les valeurs mutables et immuables

En plus de tous ces types de données, vous devez également savoir comment les objets Python peuvent être mutable ou immuable. C a un concept similaire avec des paramètres de fonction quand on parle de passe-par-valeur ou passe-par-référence. En C, tout les paramètres sont transmis par valeur. Si vous souhaitez autoriser une fonction à modifier une variable dans l'appelant, vous devez passer un pointeur vers cette variable.

Vous vous demandez peut-être si vous pouvez contourner la restriction immuable en passant simplement un objet immuable à C à l'aide d'un pointeur. Sauf si vous allez à des extrêmes moches et non portables, Python ne vous donnera pas de pointeur sur un objet, donc cela ne fonctionne tout simplement pas. Si vous souhaitez modifier un objet Python en C, vous devrez prendre des mesures supplémentaires pour y parvenir. Ces étapes dépendent des outils que vous utilisez, comme vous le verrez ci-dessous.

Ainsi, vous pouvez ajouter l'immuabilité à votre liste de contrôle des éléments à prendre en compte lors de la création de liaisons Python. Votre dernier arrêt lors de la grande tournée de création de cette liste de contrôle est de savoir comment gérer les différentes façons dont Python et C gèrent la gestion de la mémoire.

Gérer la mémoire

C et Python gérer la mémoire différemment. En C, le développeur doit gérer toutes les allocations de mémoire et s'assurer qu'elles sont libérées une seule fois. Python s'en charge pour vous en utilisant un garbage collector.

Bien que chacune de ces approches ait ses avantages, elle ajoute une ride supplémentaire à la création de liaisons Python. Vous devez être conscient de où la mémoire de chaque objet a été allouée et assurez-vous qu'il n'est libéré que du même côté de la barrière de la langue.

Par exemple, un objet Python est créé lorsque vous définissez x = 3. La mémoire pour cela est allouée du côté Python et doit être récupérée. Heureusement, avec les objets Python, il est assez difficile de faire autre chose. Jetez un œil à l'inverse en C, où vous allouez directement un bloc de mémoire:

int* iPtr = (int*)malloc(taille de(int));

Lorsque vous faites cela, vous devez vous assurer que ce pointeur est libéré en C. Cela peut signifier ajouter manuellement du code à vos liaisons Python pour ce faire.

Cela complète votre liste de contrôle de sujets généraux. Commençons par configurer votre système pour pouvoir écrire du code!

Configuration de votre environnement

Pour ce didacticiel, vous allez utiliser des bibliothèques C et C ++ préexistantes du référentiel Real Python GitHub pour afficher un test de chaque outil. L'objectif est que vous puissiez utiliser ces idées pour n'importe quelle bibliothèque C. Pour suivre tous les exemples ici, vous devez disposer des éléments suivants:

  • UNE Bibliothèque C ++ installé et connaissance du chemin pour l'appel de la ligne de commande
  • Python outils de développement:
    • Pour Linux, c'est le python3-dev ou python3-devel package, en fonction de votre distribution.
    • Pour Windows, il existe plusieurs options.
  • Python 3.6 ou plus grand
  • UNE environnement virtuel (recommandé, mais pas obligatoire)
  • le invoquer outil

Le dernier pourrait être nouveau pour vous, alors examinons-le de plus près.

En utilisant le invoquer Outil

invoquer est l'outil que vous utiliserez pour créer et tester vos liaisons Python dans ce didacticiel. Il a un objectif similaire à faire mais utilise Python au lieu de Makefiles. Vous devrez installer invoquer dans votre environnement virtuel en utilisant pépin:

$ python3 -m pip install invoke

Pour l'exécuter, vous tapez invoquer suivi de la tâche que vous souhaitez exécuter:

$ invoquer build-cmult
==================================================
= Building C Library
* Achevée

Pour voir quelles tâches sont disponibles, vous utilisez le --liste option:

$ invoke --list
Tâches disponibles:

        tous Créer et exécuter tous les tests
        build-cffi Construire les liaisons CFFI Python
        build-cmult Génère la bibliothèque partagée pour l'exemple de code C
        build-cppmult Générez la bibliothèque partagée pour l'exemple de code C ++
        build-cython Construire le module d'extension cython
        build-pybind11 Génère la bibliothèque d'encapsuleur pybind11
        nettoyer Supprimer tous les objets construits
        test-cffi Exécutez le script pour tester CFFI
        test-ctypes Exécutez le script pour tester les ctypes
        test-cython Exécutez le script pour tester Cython
        test-pybind11 Exécutez le script pour tester PyBind11

Notez que lorsque vous regardez dans le tasks.py fichier où le invoquer les tâches sont définies, vous verrez que le nom de la deuxième tâche répertoriée est build_cffi. Cependant, la sortie de --liste le montre comme build-cffi. Le signe moins (-) ne peut pas être utilisé dans le cadre d'un nom Python, le fichier utilise donc un trait de soulignement (_) au lieu.

Pour chacun des outils que vous examinerez, il y aura un construire- et un tester- tâche définie. Par exemple, pour exécuter le code de CFFI, vous pouvez taper invoquer build-cffi test-cffi. Une exception est ctypes, car il n'y a pas de phase de construction pour ctypes. De plus, deux tâches spéciales ont été ajoutées pour plus de commodité:

  • invoquer tout exécute les tâches de génération et de test pour tous les outils.
  • invoquer propre supprime tous les fichiers générés.

Maintenant que vous avez une idée de la façon d'exécuter le code, jetons un coup d'œil au code C que vous encapsulerez avant d'accéder à l'aperçu des outils.

Source C ou C ++

Dans chacune des sections d'exemple ci-dessous, vous serez création de liaisons Python pour la même fonction en C ou C ++. Ces sections sont destinées à vous donner un aperçu de l'apparence de chaque méthode, plutôt qu'un didacticiel détaillé sur cet outil, de sorte que la fonction que vous allez encapsuler est petite. La fonction pour laquelle vous allez créer des liaisons Python prend un int et un flotte comme paramètres d'entrée et renvoie un flotte c'est le produit des deux nombres:

// cmult.c
flotte cmult(int int_param, flotte float_param) 
    flotte valeur_retour = int_param * float_param;
    printf("Dans cmult: int:% d float% .1f retournant% .1f n", int_param,
            float_param, valeur_retour);
    revenir valeur_retour;

Les fonctions C et C ++ sont presque identiques, avec des différences mineures de nom et de chaîne entre elles. Vous pouvez obtenir une copie de tout le code en cliquant sur le lien ci-dessous:

Maintenant que vous avez cloné le référentiel et que vos outils sont installés, vous pouvez créer et tester les outils. Plongeons-nous dans chaque section ci-dessous!

ctypes

Vous commencerez par ctypes, qui est un outil de la bibliothèque standard pour créer des liaisons Python. Il fournit un ensemble d'outils de bas niveau pour charger des bibliothèques partagées et rassembler des données entre Python et C.

Comment il est installé

Un des gros avantages de ctypes c'est qu'il fait partie de la bibliothèque standard Python. Il a été ajouté dans Python version 2.5, il est donc fort probable que vous l'ayez déjà. Vous pouvez importation comme vous le faites avec le sys ou temps modules.

Appel de la fonction

Tout le code pour charger votre bibliothèque C et appeler la fonction sera dans votre programme Python. C'est super car il n'y a pas d'étapes supplémentaires dans votre processus. Vous exécutez simplement votre programme et tout est pris en charge. Pour créer vos liaisons Python dans ctypes, vous devez effectuer ces étapes:

  1. Charge votre bibliothèque.
  2. Emballage certains de vos paramètres d'entrée.
  3. Dire ctypes le type de retour de votre fonction.

Vous les examinerez tour à tour.

Chargement de la bibliothèque

ctypes fournit plusieurs façons de charger une bibliothèque partagée, dont certaines sont spécifiques à la plate-forme. Pour votre exemple, vous allez créer un ctypes.CDLL objet directement en passant le chemin complet vers la bibliothèque partagée que vous souhaitez:

# ctypes_test.py
importation ctypes
importation pathlib

si __Nom__ == "__principale__":
    # Charger la bibliothèque partagée dans ctypes
    libname = pathlib.Chemin().absolu() / "libcmult.so"
    c_lib = ctypes.CDLL(libname)

Cela fonctionnera pour les cas où la bibliothèque partagée se trouve dans le même répertoire que votre script Python, mais soyez prudent lorsque vous essayez de charger des bibliothèques qui proviennent de packages autres que vos liaisons Python. Il existe de nombreux détails pour le chargement des bibliothèques et la recherche de chemins dans le ctypes documentation spécifique à la plate-forme et à la situation.

Maintenant que vous avez chargé la bibliothèque en Python, vous pouvez essayer de l'appeler!

Appel de votre fonction

N'oubliez pas que le prototype de fonction pour votre fonction C est le suivant:

// cmult.h
flotte cmult(int int_param, flotte float_param);

Vous devez passer un entier et un flottant et vous pouvez vous attendre à ce qu'un flottant soit retourné. Les entiers et les flottants ont une prise en charge native à la fois en Python et en C, vous vous attendez donc à ce que ce cas fonctionne pour des valeurs raisonnables.

Une fois que vous avez chargé la bibliothèque dans vos liaisons Python, la fonction sera un attribut de c_lib, qui est le CDLL objet que vous avez créé précédemment. Vous pouvez essayer de l'appeler comme ceci:

X, y = 6, 2.3
répondre = c_lib.cmult(X, y)

Oups! Ça ne marche pas. Cette ligne est mise en commentaire dans l'exemple de dépôt car elle échoue. Si vous essayez d'exécuter avec cet appel, Python se plaindra d'une erreur:

$ invoke test-ctypes
Traceback (dernier appel le plus récent):
        Fichier "ctypes_test.py", ligne 16, dans 
                answer = c_lib.cmult (x, y)
ctypes.ArgumentError: argument 2: : Je ne sais pas comment convertir le paramètre 2

On dirait que tu dois dire ctypes sur tous les paramètres qui ne sont pas des entiers. ctypes n'a aucune connaissance de la fonction, sauf si vous le dites explicitement. Tout paramètre non marqué autrement est supposé être un entier. ctypes ne sait pas comment convertir la valeur 2.3 qui est stocké dans y à un entier, donc il échoue.

Pour résoudre ce problème, vous devrez créer un c_float du nombre. Vous pouvez le faire sur la ligne où vous appelez la fonction:

# ctypes_test.py
répondre = c_lib.cmult(X, ctypes.c_float(y))
impression(F"En Python: int: X    flotte y: .1f    retour val réponse: .1f")

Maintenant, lorsque vous exécutez ce code, il renvoie le produit des deux nombres que vous avez transmis:

$ invoke test-ctypes
    En cmult: int: 6 float 2.3 renvoyant 13.8
                En Python: int: 6 float 2.3 return val 48.0

Attends une minute… 6 multiplié par 2.3 n'est pas 48,0!

Il s'avère que, tout comme les paramètres d'entrée, ctypes suppose votre fonction renvoie un int. En réalité, votre fonction renvoie un flotte, qui est mal organisé. Tout comme le paramètre d'entrée, vous devez dire ctypes pour utiliser un type différent. La syntaxe est ici légèrement différente:

# ctypes_test.py
c_lib.cmult.retaper = ctypes.c_float
répondre = c_lib.cmult(X, ctypes.c_float(y))
impression(F"En Python: int: X    flotte y: .1f    retour val réponse: .1f")

Cela devrait faire l'affaire. Exécutons l'ensemble test-ctypes ciblez et voyez ce que vous avez. Rappelez-vous, la première section de sortie est avant vous avez corrigé le retaper de la fonction pour être un flotteur:

$ invoke test-ctypes
==================================================
= Building C Library
* Achevée
==================================================
= Test du module ctypes
                En cmult: int: 6 float 2.3 renvoyant 13.8
                En Python: int: 6 float 2.3 return val 48.0

                En cmult: int: 6 float 2.3 renvoyant 13.8
                En Python: int: 6 float 2.3 return val 13.8

C'est mieux! Alors que la première version non corrigée renvoie la mauvaise valeur, votre version fixe est compatible avec la fonction C. C et Python obtiennent le même résultat! Maintenant que cela fonctionne, regardez pourquoi vous pouvez ou non vouloir utiliser ctypes.

Forces et faiblesses

Le plus grand avantage ctypes a sur les autres outils que vous allez examiner ici, c'est que c'est intégré à la bibliothèque standard. Il ne nécessite également aucune étape supplémentaire, car tout le travail est effectué dans le cadre de votre programme Python.

De plus, les concepts utilisés sont de bas niveau, ce qui permet de gérer des exercices comme celui que vous venez de faire. Cependant, des tâches plus complexes deviennent lourdes avec le manque d'automatisation. Dans la section suivante, vous verrez un outil qui ajoute une certaine automatisation au processus.

CFFI

CFFI est le C Interface de fonction étrangère pour Python. Il faut une approche plus automatisée pour générer des liaisons Python. CFFI a plusieurs façons de créer et d'utiliser vos liaisons Python. Il y a deux options différentes à sélectionner, ce qui vous donne quatre modes possibles:

  • ABI vs API: Le mode API utilise un compilateur C pour générer un module Python complet, tandis que le mode ABI charge la bibliothèque partagée et interagit directement avec elle. Sans exécuter le compilateur, obtenir les structures et les paramètres corrects est sujet aux erreurs. La documentation recommande fortement d'utiliser le mode API.

  • en ligne vs hors ligne: La différence entre ces deux modes est un compromis entre vitesse et commodité:

    • Mode en ligne compile les liaisons Python à chaque exécution de votre script. C'est pratique, car vous n'avez pas besoin d'une étape de construction supplémentaire. Cependant, cela ralentit votre programme.
    • Mode hors ligne nécessite une étape supplémentaire pour générer les liaisons Python une seule fois, puis les utilise à chaque exécution du programme. C'est beaucoup plus rapide, mais cela peut ne pas avoir d'importance pour votre application.

Pour cet exemple, vous utiliserez le mode hors ligne de l'API, qui produit le code le plus rapide et, en général, ressemble à d'autres liaisons Python que vous créerez plus tard dans ce didacticiel.

Comment il est installé

Puisque CFFI ne fait pas partie de la bibliothèque standard, vous devrez l'installer sur votre ordinateur. Il est recommandé de créer un environnement virtuel pour cela. Heureusement, CFFI installe avec pépin:

$ python3 -m pip install cffi

Cela installera le package dans votre environnement virtuel. Si vous avez déjà installé à partir du requirements.txt, alors cela devrait être pris en charge. Vous pouvez jeter un oeil à requirements.txt en accédant au repo sur le lien ci-dessous:

Maintenant que vous avez CFFI installé, il est temps de l'essayer!

Appel de la fonction

contrairement à ctypes, avec CFFI vous créez un module Python complet. Tu pourras importation le module comme tout autre module de la bibliothèque standard. Vous devrez faire un travail supplémentaire pour créer votre module Python. Pour utiliser votre CFFI Liaisons Python, vous devrez suivre les étapes suivantes:

  • Écrire du code Python décrivant les liaisons.
  • Courir ce code pour générer un module chargeable.
  • Modifier le code appelant pour importer et utiliser votre module nouvellement créé.

Cela peut sembler beaucoup de travail, mais vous allez parcourir chacune de ces étapes et voir comment cela fonctionne.

Écrivez les liaisons

CFFI fournit des méthodes pour lire un Fichier d'en-tête C pour faire la plupart du travail lors de la génération de liaisons Python. Dans la documentation de CFFI, le code pour ce faire est placé dans un fichier Python distinct. Pour cet exemple, vous allez placer ce code directement dans l'outil de génération invoquer, qui utilise des fichiers Python en entrée. Utiliser CFFI, vous commencez par créer un cffi.FFI , qui fournit les trois méthodes dont vous avez besoin:

# tasks.py
importation cffi
...
"" "Construire les liaisons CFFI Python" ""
print_banner("Construire le module CFFI")
ffi = cffi.FFI()

Une fois que vous avez le FFI, vous utiliserez .cdef () pour traiter automatiquement le contenu du fichier d'en-tête. Cela crée des fonctions d'encapsulation pour que vous puissiez rassembler les données de Python:

# tasks.py
this_dir = pathlib.Chemin().absolu()
h_file_name = this_dir / "cmult.h"
avec ouvert(h_file_name) comme h_file:
    ffi.cdef(h_file.lis())

La lecture et le traitement du fichier d'en-tête est la première étape. Après cela, vous devez utiliser .set_source () pour décrire le fichier source CFFI générera:

# tasks.py
ffi.set_source(
    "cffi_example",
    # Puisque vous appelez directement une bibliothèque entièrement construite, aucune source personnalisée
    # est nécessaire. Vous devez cependant inclure les fichiers .h, car derrière
    # les scènes cffi génère un fichier .c qui contient un fichier compatible Python
    # wrapper autour de chacune des fonctions.
    '#include "cmult.h"',
    # L'important est d'inclure la bibliothèque pré-construite dans la liste des
    # bibliothèques avec lesquelles vous créez un lien:
    bibliothèques=[[[["cmult"],
    bibliothèque_dirs=[[[[this_dir.as_posix()],
    extra_link_args=[[[["-Wl, -rpath ,."],
)

Voici une ventilation des paramètres que vous transmettez:

  • "cffi_example" est le nom de base du fichier source qui sera créé sur votre système de fichiers. CFFI va générer un .c fichier, compilez-le dans un .o fichier et le lier à un ..donc ou ..dll fichier.

  • '#include "cmult.h"' est le code source C personnalisé qui sera inclus dans la source générée avant sa compilation. Ici, vous incluez simplement le .h fichier pour lequel vous générez des liaisons, mais cela peut être utilisé pour certaines personnalisations intéressantes.

  • bibliothèques =["cmult"] indique à l'éditeur de liens le nom de votre bibliothèque C préexistante. Il s'agit d'une liste, vous pouvez donc spécifier plusieurs bibliothèques si nécessaire.

  • bibliothèque_dirs =[this_dir.as_posix(),] est une liste de répertoires qui indique à l'éditeur de liens où rechercher la liste de bibliothèques ci-dessus.

  • extra_link_args =['-Wl,-rpath,.'] est un ensemble d'options qui génèrent un objet partagé, qui se penchera sur le chemin actuel (.) pour les autres bibliothèques à charger.

Construire les liaisons Python

Appel .set_source () ne construit pas les liaisons Python. Il configure uniquement les métadonnées pour décrire ce qui sera généré. Pour construire les liaisons Python, vous devez appeler .compiler():

Cela termine les choses en générant le .c fichier, .o et la bibliothèque partagée. le invoquer La tâche que vous venez de parcourir peut être exécutée sur la ligne de commande pour créer les liaisons Python:

$ invoquer build-cffi
==================================================
= Building C Library
* Achevée
==================================================
= Construction du module CFFI
* Achevée

Vous avez votre CFFI Liaisons Python, il est donc temps d'exécuter ce code!

Appel de votre fonction

Après tout le travail que vous avez fait pour configurer et exécuter le CFFI compilateur, l'utilisation des liaisons Python générées ressemble à l'utilisation de tout autre module Python:

# cffi_test.py
importation cffi_example

si __Nom__ == "__principale__":
    # Exemples de données pour votre appel
    X, y = 6, 2.3

    répondre = cffi_example.lib.cmult(X, y)
    impression(F"En Python: int: X    flotte y: .1f    retour val réponse: .1f")

Vous importez le nouveau module, puis vous pouvez appeler cmult () directement. Pour le tester, utilisez le test-cffi tâche:

$ invoquer test-cffi
==================================================
= Test du module CFFI
                En cmult: int: 6 float 2.3 renvoyant 13.8
                En Python: int: 6 float 2.3 return val 13.8

Cela exécute votre cffi_test.py qui teste les nouvelles liaisons Python que vous avez créées avec CFFI. Cela complète la section sur la rédaction et l'utilisation de votre CFFI Liaisons Python.

Forces et faiblesses

Il pourrait sembler que ctypes nécessite moins de travail que le CFFI exemple que vous venez de voir. Bien que cela soit vrai pour ce cas d'utilisation, CFFI s'adapte bien mieux aux grands projets ctypes en raison de automatisation d'une grande partie de l'habillage de fonction.

CFFI produit également une expérience utilisateur très différente. ctypes vous permet de charger une bibliothèque C préexistante directement dans votre programme Python. CFFI, d'autre part, crée un nouveau module Python qui peut être chargé comme les autres modules Python.

De plus, avec le API hors ligne méthode que vous avez utilisée ci-dessus, la pénalité de temps pour la création des liaisons Python est effectuée une fois lorsque vous la créez et ne se produit pas chaque fois que vous exécutez votre code. Pour les petits programmes, ce n'est peut-être pas un gros problème, mais CFFI s'adapte également mieux aux projets de plus grande envergure.

Comme ctypes, en utilisant CFFI vous permet uniquement d'interfacer directement avec les bibliothèques C. Les bibliothèques C ++ nécessitent beaucoup de travail à utiliser. Dans la section suivante, vous verrez un outil de liaisons Python qui se concentre sur C ++.

PyBind11

PyBind11 adopte une approche assez différente pour créer des liaisons Python. En plus de déplacer le focus de C vers C ++, il a également utilise C ++ pour spécifier et construire le module, lui permettant de bénéficier des outils de métaprogrammation en C ++. Comme CFFI, les liaisons Python générées à partir de PyBind11 sont un module Python complet qui peut être importé et utilisé directement.

PyBind11 est calqué sur le Boost :: Python bibliothèque et a une interface similaire. Il limite cependant son utilisation au C ++ 11 et aux versions plus récentes, ce qui lui permet de simplifier et d'accélérer les choses par rapport à Boost, qui prend tout en charge.

Comment il est installé

La section Premiers pas du PyBind11 la documentation vous explique comment télécharger et créer les cas de test pour PyBind11. Bien que cela ne semble pas être strictement requis, le fait de suivre ces étapes garantira que vous disposez des outils C ++ et Python appropriés.

Vous souhaiterez installer cet outil dans votre environnement virtuel:

$ python3 -m pip install pybind11

PyBind11 est une bibliothèque entièrement en-tête, similaire à une grande partie de Boost. Ceci permet pépin pour installer la source C ++ réelle de la bibliothèque directement dans votre environnement virtuel.

Appel de la fonction

Avant de plonger, veuillez noter que vous utilisez un autre fichier source C ++, cppmult.cpp, au lieu du fichier C que vous avez utilisé pour les exemples précédents. La fonction est essentiellement la même dans les deux langues.

Rédaction des liaisons

Semblable à CFFI, vous devez créer du code pour indiquer à l'outil comment créer vos liaisons Python. contrairement à CFFI, ce code sera en C ++ au lieu de Python. Heureusement, une quantité minimale de code est requise:

// pybind11_wrapper.cpp
#comprendre 
#comprendre 

PYBIND11_MODULE(pybind11_example, m) 
    m.doc() = "exemple de plugin pybind11"; // Docstring de module optionnel
    m.def("cpp_function", &cppmult, "Une fonction qui multiplie deux nombres");

Regardons cela un morceau à la fois, comme PyBind11 regroupe de nombreuses informations en quelques lignes.

Les deux premières lignes incluent le pybind11.h fichier et le fichier d'en-tête de votre bibliothèque C ++, cppmult.hpp. Après cela, vous avez le PYBIND11_MODULE macro. Cela se développe en un bloc de code C ++ bien décrit dans le PyBind11 la source:

Cette macro crée le point d'entrée qui sera invoqué lorsque l'interpréteur Python importera un module d'extension. Le nom du module est donné comme premier argument et ne doit pas être entre guillemets. Le deuxième argument macro définit une variable de type py :: module qui peut être utilisé pour initialiser le module. (La source)

Cela signifie pour vous que, pour cet exemple, vous créez un module appelé pybind11_example et que le reste du code utilisera m comme nom du py :: module objet. Sur la ligne suivante, à l'intérieur de la fonction C ++ que vous définissez, vous créez une docstring pour le module. Bien que cela soit facultatif, c'est une bonne touche pour rendre votre module plus Pythonic.

Enfin, vous avez le m.def () appel. Cela définira une fonction qui sera exportée par vos nouvelles liaisons Python, ce qui signifie qu'elle sera visible depuis Python. Dans cet exemple, vous passez trois paramètres:

  • cpp_function est le nom exporté de la fonction que vous utiliserez en Python. Comme le montre cet exemple, il n'a pas besoin de correspondre au nom de la fonction C ++.
  • & cppmult prend l'adresse de la fonction à exporter.
  • "Une fonction..." est une docstring optionnelle pour la fonction.

Maintenant que vous avez le code pour les liaisons Python, regardez comment vous pouvez l'intégrer dans un module Python.

Construire les liaisons Python

L'outil que vous utilisez pour créer les liaisons Python dans PyBind11 est le compilateur C ++ lui-même. Vous devrez peut-être modifier les paramètres par défaut de votre compilateur et de votre système d'exploitation.

Pour commencer, vous devez créer la bibliothèque C ++ pour laquelle vous créez des liaisons. Pour un exemple aussi petit, vous pouvez créer le cppmult directement dans la bibliothèque de liaisons Python. Cependant, pour la plupart des exemples du monde réel, vous aurez une bibliothèque préexistante que vous souhaitez encapsuler, donc vous allez créer le cppmult bibliothèque séparément. La build est un appel standard au compilateur pour construire une bibliothèque partagée:

# tasks.py
invoquer.courir(
    "g ++ -O3 -Wall -Werror -shared -std = c ++ 11 -fPIC cppmult.cpp"
    "-o libcppmult.so"
)

Exécuter cela avec invoquer build-cppmult produit libcppmult.so:

$ invoquer build-cppmult
==================================================
= Création d'une bibliothèque C ++
* Achevée

La construction des liaisons Python, en revanche, nécessite des détails particuliers:

    1 # tasks.py
    2 invoquer.courir(
    3     "g ++ -O3 -Wall -Werror -shared -std = c ++ 11 -fPIC"
    4     "` python3 -m pybind11 --includes` "
    5     "-I /usr/include/python3.7 -I."
    6     "0    "
    sept     "-o 1`python3.7-config --extension-suffix`"
    8     "-L. -Lcppmult -Wl, -rpath ,.".format(cpp_name, nom_extension)
    9 )

Parcourons cette ligne par ligne. Ligne 3 contient des indicateurs de compilateur C ++ assez standard qui indiquent plusieurs détails, notamment que vous souhaitez que tous les avertissements soient interceptés et traités comme des erreurs, que vous souhaitiez une bibliothèque partagée et que vous utilisez C ++ 11.

Ligne 4 est la première étape de la magie. Il appelle pybind11 module pour le faire produire le bon comprendre chemins pour PyBind11. Vous pouvez exécuter cette commande directement sur la console pour voir ce qu'elle fait:

$ python3 -m pybind11 --inclut
-I / home / jima / .virtualenvs / realpython / include / python3.7m
-I / home / jima / .virtualenvs / realpython / include / site / python3.7

Votre sortie doit être similaire mais afficher des chemins différents.

Dans ligne 5 de votre appel de compilation, vous pouvez voir que vous ajoutez également le chemin d'accès au développeur Python comprend. Bien qu'il soit recommandé de ne fais pas lien contre la bibliothèque Python elle-même, la source a besoin de code de Python.h pour travailler sa magie. Heureusement, le code qu'il utilise est assez stable dans toutes les versions de Python.

La ligne 5 utilise également -JE . pour ajouter le répertoire courant à la liste des comprendre chemins. Cela permet au #comprendre ligne dans votre code wrapper à résoudre.

Ligne 6 spécifie le nom de votre fichier source, qui est pybind11_wrapper.cpp. Puis, le ligne 7 vous voyez un peu plus de magie de construction se produire. Cette ligne spécifie le nom du fichier de sortie. Python a des idées particulières sur la dénomination des modules, qui incluent la version Python, l'architecture de la machine et d'autres détails. Python fournit également un outil pour aider à cela appelé python3.7-config:

$ python3.7-config --extension-suffix
.cpython-37m-x86_64-linux-gnu.so

Vous devrez peut-être modifier la commande si vous utilisez une version différente de Python. Vos résultats changeront probablement si vous utilisez une version différente de Python ou si vous utilisez un système d'exploitation différent.

La dernière ligne de votre commande de construction, ligne 8, pointe l'éditeur de liens vers libcppmult bibliothèque que vous avez construite plus tôt. le rpath La section indique à l'éditeur de liens d'ajouter des informations à la bibliothèque partagée pour aider le système d'exploitation à trouver libcppmult lors de l'exécution. Enfin, vous remarquerez que cette chaîne est formatée avec le cpp_name et le nom_extension. Vous utiliserez à nouveau cette fonction lorsque vous construirez votre module de liaisons Python avec Cython dans la section suivante.

Exécutez cette commande pour créer vos liaisons:

$ invoquer build-pybind11
==================================================
= Création d'une bibliothèque C ++
* Achevée
==================================================
= Construction du module PyBind11
* Achevée

C'est ça! Vous avez construit vos liaisons Python avec PyBind11. Il est temps de le tester!

Appel de votre fonction

Semblable à la CFFI exemple ci-dessus, une fois que vous avez fait le gros du travail de création des liaisons Python, appeler votre fonction ressemble à du code Python normal:

# pybind11_test.py
importation pybind11_example

si __Nom__ == "__principale__":
    # Exemples de données pour votre appel
    X, y = 6, 2.3

    répondre = pybind11_example.cpp_function(X, y)
    impression(F"En Python: int: X    flotte y: .1f    retour val réponse: .1f")

Depuis que vous avez utilisé pybind11_example comme nom de votre module dans le PYBIND11_MODULE c'est le nom que vous importez. dans le m.def () t'appelle dit PyBind11 pour exporter le cppmult fonctionne comme cpp_function, c'est donc ce que vous utilisez pour l'appeler depuis Python.

Vous pouvez le tester avec invoquer ainsi que:

$ invoquer test-pybind11
==================================================
= Test du module PyBind11
                Dans cppmul: int: 6 float 2.3 renvoyant 13.8
                En Python: int: 6 float 2.3 return val 13.8

C'est ce que PyBind11 ressemble à. Ensuite, vous verrez quand et pourquoi PyBind11 est le bon outil pour le travail.

Forces et faiblesses

PyBind11 se concentre sur C ++ au lieu de C, ce qui le rend différent de ctypes et CFFI. Il a plusieurs fonctionnalités qui le rendent assez attrayant pour les bibliothèques C ++:

  • Elle supporte Des classes.
  • Il gère sous-classement polymorphe.
  • Il vous permet d'ajouter attributs dynamiques aux objets de Python et de nombreux autres outils, ce qui serait assez difficile à faire à partir des outils basés sur C que vous avez examinés.

That being said, there’s a fair bit of setup and configuration you need to do to get PyBind11 up and running. Getting the installation and build correct can be a bit finicky, but once that’s done, it seems fairly solid. Aussi, PyBind11 requires that you use at least C++11 or newer. This is unlikely to be a big restriction for most projects, but it may be a consideration for you.

Finally, the extra code you need to write to create the Python bindings is in C++ and not Python. This may or may not be an issue for you, but it est different than the other tools you’ve looked at here. In the next section, you’ll move on to Cython, which takes quite a different approach to this problem.

Cython

The approach Cython takes to creating Python bindings uses a Python-like language to define the bindings and then generates C or C++ code that can be compiled into the module. There are several methods for building Python bindings with Cython. The most common one is to use setup de distutils. For this example, you’ll stick with the invoke tool, which will allow you to play with the exact commands that are run.

How It’s Installed

Cython is a Python module that can be installed into your virtual environment from PyPI:

$ python3 -m pip install cython

Again, if you’ve installed the requirements.txt file into your virtual environment, then this will already be there. You can grab a copy of requirements.txt by clicking on the link below:

That should have you ready to work with Cython!

Calling the Function

To build your Python bindings with Cython, you’ll follow similar steps to those you used for CFFI et PyBind11. You’ll write the bindings, build them, and then run Python code to call them. Cython can support both C and C++. For this example, you’ll use the cppmult library that you used for the PyBind11 example above.

Write the Bindings

The most common form of declaring a module in Cython is to use a .pyx fichier:

    1 # cython_example.pyx
    2 """ Example cython interface definition """
    3 
    4 cdef extern de "cppmult.hpp":
    5     float cppmult(int int_param, float float_param)
    6 
    sept def pymult( int_param, float_param ):
    8     revenir cppmult( int_param, float_param )

There are two sections here:

  1. Lines 3 and 4 dire Cython that you’re using cppmult() de cppmult.hpp.
  2. Lines 6 and 7 create a wrapper function, pymult(), to call cppmult().

The language used here is a special mix of C, C++, and Python. It will look fairly familiar to Python developers, though, as the goal is to make the process easier.

The first section with cdef extern... tells Cython that the function declarations below are also found in the cppmult.hpp fichier. This is useful for ensuring that your Python bindings are built against the same declarations as your C++ code. The second section looks like a regular Python function—because it is! This section creates a Python function that has access to the C++ function cppmult.

Now that you’ve got the Python bindings defined, it’s time to build them!

Build the Python Bindings

The build process for Cython has similarities to the one you used for PyBind11. You first run Cython on the .pyx file to generate a .cpp fichier. Once you’ve done this, you compile it with the same function you used for PyBind11:

    1 # tasks.py
    2 def compile_python_module(cpp_name, extension_name):
    3     invoke.run(
    4         "g++ -O3 -Wall -Werror -shared -std=c++11 -fPIC "
    5         "`python3 -m pybind11 --includes` "
    6         "-I /usr/include/python3.7 -I .  "
    sept         "0    "
    8         "-o 1`python3.7-config --extension-suffix` "
    9         "-L. -lcppmult -Wl,-rpath,.".format(cpp_name, extension_name)
dix     )
11 
12 def build_cython(c):
13     """ Build the cython extension module """
14     print_banner("Building Cython Module")
15     # Run cython on the pyx file to create a .cpp file
16     invoke.run("cython --cplus -3 cython_example.pyx -o cython_wrapper.cpp")
17 
18     # Compile and link the cython wrapper library
19     compile_python_module("cython_wrapper.cpp", "cython_example")
20     impression("* Complete")

You start by running cython on your .pyx fichier. There are a few options you use on this command:

  • --cplus tells the compiler to generate a C++ file instead of a C file.
  • -3 switches Cython to generate Python 3 syntax instead of Python 2.
  • -o cython_wrapper.cpp specifies the name of the file to generate.

Once the C++ file is generated, you use the C++ compiler to generate the Python bindings, just as you did for PyBind11. Note that the call to produce the extra comprendre paths using the pybind11 tool is still in that function. It won’t hurt anything here, as your source will not need those.

Running this task in invoke produces this output:

$ invoke build-cython
==================================================
= Building C++ Library
* Complete
==================================================
= Building Cython Module
* Complete

You can see that it builds the cppmult library and then builds the cython module to wrap it. Now you have the Cython Python bindings. (Try saying cette quickly…) It’s time to test it out!

Calling Your Function

The Python code to call your new Python bindings is quite similar to what you used to test the other modules:

    1 # cython_test.py
    2 import cython_example
    3 
    4 # Sample data for your call
    5 X, y = 6, 2.3
    6 
    sept répondre = cython_example.pymult(X, y)
    8 impression(F"    In Python: int: x    float y:.1f    return val answer:.1f")

Line 2 imports your new Python bindings module, and you call pymult() on line 7. Remember that the .pyx file provided a Python wrapper around cppmult() and renamed it to pymult. Using invoke to run your test produces the following:

$ invoke test-cython
==================================================
= Testing Cython Module
                In cppmul: int: 6 float 2.3 returning  13.8
                In Python: int: 6 float 2.3 return val 13.8

You get the same result as before!

Strengths and Weaknesses

Cython is a relatively complex tool that can provide you a deep level of control when creating Python bindings for either C or C++. Though you didn’t cover it in depth here, it provides a Python-esque method for writing code that manually controls the GIL, which can significantly speed up certain types of problems.

That Python-esque language is not quite Python, however, so there’s a slight learning curve when you’re coming up to speed in figuring out which parts of C and Python fit where.

Other Solutions

While researching this tutorial, I came across several different tools and options for creating Python bindings. While I limited this overview to some of the more common options, there are several other tools I stumbled across. The list below is not comprehensive. It’s merely a sampling of other possibilities if one of the above tools doesn’t fit your project.

PyBindGen

PyBindGen generates Python bindings for C or C++ and is written in Python. It’s targeted at producing readable C or C++ code, which should simplify debugging issues. It wasn’t clear if this has been updated recently, as the documentation lists Python 3.4 as the latest tested version. There have been yearly releases for the last several years, however.

Boost.Python

Boost.Python has an interface similar to PyBind11, which you saw above. That’s not a coincidence, as PyBind11 was based on this library! Boost.Python is written in full C++ and supports most, if not all, versions of C++ on most platforms. In contrast, PyBind11 restricts itself to modern C++.

SIP

SIP is a toolset for generating Python bindings that was developed for the PyQt project. It’s also used by the wxPython project to generate their bindings, as well. It has a code generation tool and an extra Python module that provides support functions for the generated code.

Cppyy

cppyy is an interesting tool that has a slightly different design goal than what you’ve seen so far. In the words of the package author:

“The original idea behind cppyy (going back to 2001), was to allow Python programmers that live in a C++ world access to those C++ packages, without having to touch C++ directly (or wait for the C++ developers to come around and provide bindings).” (Source)

Shiboken

Shiboken is a tool for generating Python bindings that’s developed for the PySide project associated with the Qt project. While it was designed as a tool for that project, the documentation indicates that it’s neither Qt- nor PySide-specific and is usable for other projects.

SWIG

SWIG is a different tool than any of the others listed here. It’s a general tool used to create bindings to C and C++ programs for many other languages, not just Python. This ability to generate bindings for different languages can be quite useful in some projects. It, of course, comes with a cost as far as complexity is concerned.

Conclusion

Félicitations! You’ve now had an overview of several different options for creating Python bindings. You’ve learned about marshalling data and issues you need to consider when creating bindings. You’ve seen what it takes to be able to call a C or C++ function from Python using the following tools:

  • ctypes
  • CFFI
  • PyBind11
  • Cython

You now know that, while ctypes allow you to load a DLL or shared library directly, the other three tools take an extra step, but still create a full Python module. As a bonus, you’ve also played a little with the invoke tool to run command-line tasks from Python. You can get all of the code you saw in this tutorial by clicking the link below:

Now pick your favorite tool and start building those Python bindings! Special thanks to Loic Domaigne for the extra technical review of this tutorial.