Je veux appeler une bibliothèque C à partir d'une application Python. Je ne veux pas envelopper l'API entière, seulement les fonctions et les types de données qui sont pertinents pour mon cas. Selon moi, j'ai trois choix:
- Créez un module d'extension réel en C. Probablement exagéré, et j'aimerais également éviter la surcharge d'apprentissage de l'écriture d'extension.
- Utilisez Cython pour exposer les parties pertinentes de la bibliothèque C à Python.
- Faites le tout en Python, en utilisant
ctypes
pour communiquer avec la bibliothèque externe.
Je ne sais pas si 2) ou 3) est le meilleur choix. L'avantage de 3) est qu'il ctypes
fait partie de la bibliothèque standard, et le code résultant serait du pur Python - même si je ne suis pas sûr de la taille réelle de cet avantage.
Y a-t-il plus d'avantages / inconvénients avec l'un ou l'autre choix? Quelle approche recommandez-vous?
Edit: Merci pour toutes vos réponses, elles fournissent une bonne ressource pour quiconque cherche à faire quelque chose de similaire. La décision, bien sûr, doit encore être prise pour le cas unique — il n'y a pas une seule sorte de réponse "C'est la bonne chose". Pour mon propre cas, j'irai probablement avec des ctypes, mais j'ai aussi hâte d'essayer Cython dans un autre projet.
En l'absence d'une seule vraie réponse, l'accepter est quelque peu arbitraire; J'ai choisi la réponse de FogleBird car elle donne un bon aperçu des ctypes et c'est actuellement la réponse la plus votée. Cependant, je suggère de lire toutes les réponses pour avoir un bon aperçu.
Merci encore.
Réponses:
ctypes
est votre meilleur pari pour le faire rapidement, et c'est un plaisir de travailler avec comme vous écrivez toujours Python!J'ai récemment enveloppé un pilote FTDI pour communiquer avec une puce USB à l'aide de types et c'était génial. J'ai tout fait et travaillé en moins d'une journée de travail. (Je n'ai implémenté que les fonctions dont nous avions besoin, environ 15 fonctions).
Nous utilisions auparavant un module tiers, PyUSB , dans le même but. PyUSB est un module d'extension C / Python réel. Mais PyUSB ne libérait pas le GIL lors du blocage des lectures / écritures, ce qui nous posait des problèmes. J'ai donc écrit notre propre module en utilisant ctypes, ce qui libère le GIL lors de l'appel des fonctions natives.
Une chose à noter est que les ctypes ne connaîtront pas les
#define
constantes et les éléments de la bibliothèque que vous utilisez, uniquement les fonctions, vous devrez donc redéfinir ces constantes dans votre propre code.Voici un exemple de la façon dont le code a fini par apparaître (beaucoup ont été coupés, essayant juste de vous en montrer l'essentiel):
Quelqu'un l'a fait des repères sur les différentes options.
Je serais peut-être plus hésitant si je devais envelopper une bibliothèque C ++ avec beaucoup de classes / modèles / etc. Mais ctypes fonctionne bien avec les structures et peut même être rappelé en Python.
la source
ctypes
(pourpyinotify
), mais j'aimerais mieux comprendre le problème.One thing to note is that ctypes won't know about #define constants and stuff in the library you're using, only the functions, so you'll have to redefine those constants in your own code.
Donc, je dois définir les constantes qui sont là danswinioctl.h
....ctypes
est beaucoup plus lent que l'extension c car le goulot d'étranglement est l'interface de Python à CAttention: un avis d'un développeur Cython core à venir.
Je recommande presque toujours Cython aux ctypes. La raison en est qu'il a un chemin de mise à niveau beaucoup plus fluide. Si vous utilisez des ctypes, beaucoup de choses seront simples au début, et c'est certainement cool d'écrire votre code FFI en Python ordinaire, sans compilation, sans construire de dépendances et tout ça. Cependant, à un certain moment, vous constaterez presque certainement que vous devez appeler beaucoup dans votre bibliothèque C, soit en boucle soit dans une série plus longue d'appels interdépendants, et vous voudriez accélérer cela. C'est le point où vous remarquerez que vous ne pouvez pas faire cela avec des ctypes. Ou, lorsque vous avez besoin de fonctions de rappel et que vous trouvez que votre code de rappel Python devient un goulot d'étranglement, vous souhaitez l'accélérer et / ou le déplacer vers le bas en C également. Encore une fois, vous ne pouvez pas faire cela avec des ctypes.
Avec Cython, OTOH, vous êtes totalement libre de rendre le code d'encapsulation et d'appel aussi fin ou épais que vous le souhaitez. Vous pouvez commencer par de simples appels dans votre code C à partir du code Python normal, et Cython les traduira en appels C natifs, sans surcharge d'appel supplémentaire, et avec une surcharge de conversion extrêmement faible pour les paramètres Python. Lorsque vous remarquez que vous avez besoin de plus de performances à un moment où vous effectuez trop d'appels coûteux dans votre bibliothèque C, vous pouvez commencer à annoter votre code Python environnant avec des types statiques et laisser Cython l'optimiser directement en C pour vous. Ou, vous pouvez commencer à réécrire des parties de votre code C en Cython afin d'éviter les appels et de spécialiser et de resserrer vos boucles de manière algorithmique. Et si vous avez besoin d'un rappel rapide, il suffit d'écrire une fonction avec la signature appropriée et de la passer directement dans le registre de rappel C. Encore une fois, pas de surcharge, et cela vous donne des performances d'appel C simples. Et dans le cas beaucoup moins probable où vous ne pouvez vraiment pas obtenir votre code assez rapidement en Cython, vous pouvez toujours envisager de réécrire les parties vraiment critiques de celui-ci en C (ou C ++ ou Fortran) et l'appeler à partir de votre code Cython naturellement et nativement. Mais alors, cela devient vraiment le dernier recours au lieu de la seule option.
Donc, ctypes est agréable de faire des choses simples et de faire fonctionner rapidement quelque chose. Cependant, dès que les choses commencent à croître, vous arriverez très probablement au point où vous remarquerez que vous feriez mieux d'utiliser Cython dès le début.
la source
__radd__
). C'est particulièrement gênant lorsque vous prévoyez que votre classe interagisse avec les types intégrés (par exemple,int
etfloat
). De plus, les méthodes magiques dans cython sont juste un peu boguées en général.Cython est un outil assez cool en soi, qui mérite d'être étudié, et est étonnamment proche de la syntaxe Python. Si vous faites du calcul scientifique avec Numpy, alors Cython est la voie à suivre car il s'intègre à Numpy pour des opérations matricielles rapides.
Cython est un sur-ensemble du langage Python. Vous pouvez y jeter n'importe quel fichier Python valide, et il crachera un programme C valide. Dans ce cas, Cython mappera simplement les appels Python à l'API CPython sous-jacente. Cela entraîne peut-être une accélération de 50% car votre code n'est plus interprété.
Pour obtenir des optimisations, vous devez commencer à indiquer à Cython des faits supplémentaires sur votre code, tels que les déclarations de type. Si vous en dites assez, cela peut faire bouillir le code en C. pur. Autrement dit, une boucle for en Python devient une boucle for en C. Ici, vous verrez des gains de vitesse massifs. Vous pouvez également créer un lien vers des programmes C externes ici.
L'utilisation du code Cython est également incroyablement facile. Je pensais que le manuel rendait le son difficile. Vous faites littéralement:
puis vous pouvez
import mymodule
dans votre code Python et oublier complètement qu'il se compile en C.Dans tous les cas, parce que Cython est si facile à configurer et à utiliser, je suggère de l'essayer pour voir s'il convient à vos besoins. Ce ne sera pas un gaspillage s'il s'avère que ce n'est pas l'outil que vous recherchez.
la source
mymod.pyx
module, puis faitesimport pyximport; pyximport.install(); import mymod
-le et la compilation se déroule en arrière-plan.runcython mymodule.pyx
. Et contrairement à pyximport, vous pouvez l'utiliser pour des tâches de liaison plus exigeantes. La seule mise en garde est que je suis celui qui a écrit les 20 lignes de bash pour cela et pourrait être biaisé.Pour appeler une bibliothèque C à partir d'une application Python, il existe également cffi qui est une nouvelle alternative pour les ctypes . Il apporte un nouveau regard sur FFI:
la source
Je vais en jeter un autre là-bas: SWIG
Il est facile à apprendre, fait beaucoup de choses correctement et prend en charge beaucoup plus de langues, donc le temps passé à l'apprendre peut être très utile.
Si vous utilisez SWIG, vous créez un nouveau module d'extension python, mais SWIG fait la plupart du travail pour vous.
la source
Personnellement, j'écrirais un module d'extension en C. Ne vous laissez pas intimider par les extensions Python C - elles ne sont pas du tout difficiles à écrire. La documentation est très claire et utile. Quand j'ai écrit pour la première fois une extension C en Python, je pense qu'il m'a fallu environ une heure pour comprendre comment en écrire une - pas beaucoup de temps du tout.
la source
ctypes est idéal lorsque vous avez déjà un blob de bibliothèque compilé à gérer (comme les bibliothèques de système d'exploitation). La surcharge d'appel est cependant sévère, donc si vous faites beaucoup d'appels dans la bibliothèque, et que vous allez quand même écrire le code C (ou au moins le compiler), je dirais d'aller cython . Ce n'est pas beaucoup plus de travail, et ce sera beaucoup plus rapide et plus pythonique d'utiliser le fichier pyd résultant.
Personnellement, j'ai tendance à utiliser cython pour des accélérations rapides de code python (les boucles et les comparaisons d'entiers sont deux domaines où cython brille particulièrement), et quand il y aura du code / habillage d'autres bibliothèques impliqués, je me tournerai vers Boost.Python . Boost.Python peut être difficile à configurer, mais une fois que vous l'avez fait fonctionner, cela rend le code C / C ++ simple.
cython est également excellent pour emballer numpy (ce que j'ai appris des procédures de SciPy 2009 ), mais je n'ai pas utilisé numpy, donc je ne peux pas en parler.
la source
Si vous avez déjà une bibliothèque avec une API définie, je pense
ctypes
c'est la meilleure option, car il vous suffit de faire une petite initialisation puis d'appeler plus ou moins la bibliothèque comme vous en avez l'habitude.Je pense que Cython ou la création d'un module d'extension en C (ce qui n'est pas très difficile) sont plus utiles lorsque vous avez besoin de nouveau code, par exemple en appelant cette bibliothèque et en effectuant des tâches complexes et longues, puis en transmettant le résultat à Python.
Une autre approche, pour les programmes simples, est de faire directement un processus différent (compilé en externe), de sortir le résultat sur une sortie standard et de l'appeler avec le module de sous-processus. Parfois, c'est l'approche la plus simple.
Par exemple, si vous créez un programme de console C qui fonctionne plus ou moins de cette façon
Vous pourriez l'appeler depuis Python
Avec un petit formatage de chaîne, vous pouvez prendre le résultat comme vous le souhaitez. Vous pouvez également capturer la sortie d'erreur standard, donc c'est assez flexible.
la source
shell=True
pourrait facilement entraîner une sorte d'exploitation lorsqu'un utilisateur obtient vraiment un shell. C'est bien quand le développeur est le seul utilisateur, mais dans le monde, il y a tout un tas de piqûres ennuyeuses qui attendent juste quelque chose comme ça.Il y a un problème qui m'a fait utiliser des ctypes et non du cython et qui n'est pas mentionné dans d'autres réponses.
En utilisant ctypes, le résultat ne dépend pas du tout du compilateur que vous utilisez. Vous pouvez écrire une bibliothèque en utilisant plus ou moins n'importe quelle langue qui peut être compilée en bibliothèque partagée native. Peu importe quel système, quelle langue et quel compilateur. Cython, cependant, est limité par l'infrastructure. Par exemple, si vous souhaitez utiliser le compilateur Intel sur Windows, il est beaucoup plus difficile de faire fonctionner Cython: vous devez "expliquer" le compilateur à Cython, recompiler quelque chose avec ce compilateur exact, etc. Ce qui limite considérablement la portabilité.
la source
Si vous ciblez Windows et choisissez d'envelopper certaines bibliothèques C ++ propriétaires, vous découvrirez peut-être bientôt que différentes versions de
msvcrt***.dll
(Visual C ++ Runtime) sont légèrement incompatibles.Cela signifie que vous ne pourrez peut-être pas l'utiliser
Cython
car le résultatwrapper.pyd
est lié àmsvcr90.dll
(Python 2.7) oumsvcr100.dll
(Python 3.x) . Si la bibliothèque que vous encapsulez est liée à une version différente de l'exécution, vous n'avez pas de chance.Ensuite, pour que les choses fonctionnent, vous devrez créer des wrappers C pour les bibliothèques C ++, liez cette DLL de wrapper à la même version
msvcrt***.dll
que celle de votre bibliothèque C ++. Ensuite, utilisezctypes
pour charger dynamiquement votre DLL d'emballage à la main au moment de l'exécution.Il y a donc beaucoup de petits détails, qui sont décrits en détail dans l'article suivant:
"Belles bibliothèques natives (en Python) ": http://lucumr.pocoo.org/2013/8/18/beautiful-native-libraries/
la source
Il existe également une possibilité d'utiliser GObject Introspection pour les bibliothèques qui utilisent GLib .
la source
Je sais que c'est une vieille question, mais cette chose revient sur Google lorsque vous recherchez des trucs comme
ctypes vs cython
, et la plupart des réponses ici sont écrites par ceux qui sont déjà compétentscython
ouc
qui pourraient ne pas refléter le temps réel que vous avez dû investir pour apprendre ces pour implémenter votre solution. Je suis un débutant complet dans les deux. Je n'ai jamais touchécython
auparavant et j'ai très peu d'expériencec/c++
.Au cours des deux derniers jours, je cherchais un moyen de déléguer une partie très performante de mon code à quelque chose de plus bas niveau que python. J'ai implémenté mon code à la fois dans
ctypes
etCython
, qui consistait essentiellement en deux fonctions simples.J'avais une énorme liste de chaînes à traiter. Remarquez
list
etstring
. Les deux types ne correspondent pas parfaitement aux types dec
, car les chaînes python sont par défaut unicode et lesc
chaînes ne le sont pas. Les listes en python ne sont simplement PAS des tableaux de c.Voici mon verdict. Utilisez
cython
. Il s'intègre plus couramment à python et est plus facile à utiliser en général. Quand quelque chose ne va pas,ctypes
vous lancez une erreur de segmentation, au moinscython
vous donnera des avertissements de compilation avec une trace de pile chaque fois que cela est possible, et vous pouvez retourner facilement un objet python valide aveccython
.Voici un compte rendu détaillé du temps qu'il me fallait pour investir dans les deux pour mettre en œuvre la même fonction. J'ai d'ailleurs fait très peu de programmation C / C ++:
Ctypes:
c
code fonctionne.c
code.c
code dans la base de code réelle et j'ai vu quectypes
cela ne fonctionnait pas bien avec lemultiprocessing
module car son gestionnaire n'était pas sélectionnable par défaut.multiprocessing
module et réessayé.c
code a généré des erreurs de segmentation dans ma base de code bien qu'elle ait réussi mon code de test. Eh bien, c'est probablement ma faute pour ne pas bien vérifier les cas de bord, je cherchais une solution rapide.c
code et à la deuxième ou troisième itération de la boucle python qui l'utilise, j'avais unUnicodeError
problème de ne pas décoder un octet à une certaine position bien que j'encode et décode tout explicitement.À ce stade, j'ai décidé de rechercher une alternative et j'ai décidé d'examiner
cython
:setuptools
au lieu dedistutils
.setup.py
pour utiliser le module compilé dans ma base de code.multiprocessing
version de codebase. Ça marche.Pour mémoire, je n'ai bien sûr pas mesuré les délais exacts de mon investissement. Il se peut très bien que ma perception du temps soit un peu trop attentive en raison de l'effort mental requis pendant que je traitais avec des types. Mais il faut donner la sensation de traiter
cython
etctypes
la source