web-dev-qa-db-fra.com

Envelopper une bibliothèque C en Python: C, Cython ou ctypes?

Je souhaite appeler une bibliothèque C à partir d'une application Python. Je ne souhaite pas envelopper l'ensemble de l'API, mais uniquement les fonctions et les types de données pertinents pour mon cas. À mon avis, avoir trois choix:

  1. Créez un module d'extension réel dans C. Probablement exagéré, et j'aimerais également éviter les frais généraux liés à l'apprentissage de l'écriture d'extension.
  2. Utilisez Cython pour exposer les pièces pertinentes de la bibliothèque C à Python.
  3. Faites tout en Python, en utilisant ctypes pour communiquer avec la bibliothèque externe.

Je ne suis pas sûr que 2) ou 3) soit le meilleur choix. L'avantage de 3) est que ctypes fait partie de la bibliothèque standard et que le code résultant serait pur Python - bien que je ne sois pas sûr de la taille de cet avantage. .

Y at-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 constituent une bonne ressource pour ceux qui souhaitent faire quelque chose de similaire. Bien entendu, la décision reste à prendre pour le cas d'espèce - il n'y a pas de réponse du type "c'est la bonne chose". Pour mon propre cas, je vais probablement utiliser ctypes, mais je suis également impatient d'essayer Cython dans un autre projet.

En l'absence de vraie réponse, en accepter une est quelque peu arbitraire; J'ai choisi la réponse de FogleBird, car elle fournit de bonnes informations sur les types et qu'elle 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.

265
balpha

ctypes est votre meilleur moyen de le faire rapidement, et c'est un plaisir de travailler avec vous car vous écrivez toujours en Python!

J'ai récemment emballé un pilote FTDI pour communiquer avec une puce USB à l'aide de ctypes et c'était génial. J'avais tout fait et travaillais en moins d'une journée de travail. (J'ai seulement implémenté 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 publiait pas le GIL en bloquant les lectures/écritures, ce qui nous posait des problèmes. J'ai donc écrit notre propre module en utilisant ctypes, qui publie le GIL lors de l'appel des fonctions natives.

Une chose à noter est que les types ne connaîtront pas #define constantes et é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 paraître (beaucoup d'extraits, essayant simplement de vous en montrer l'essentiel):

from ctypes import *

d2xx = WinDLL('ftd2xx')

OK = 0
INVALID_HANDLE = 1
DEVICE_NOT_FOUND = 2
DEVICE_NOT_OPENED = 3

...

def openEx(serial):
    serial = create_string_buffer(serial)
    handle = c_int()
    if d2xx.FT_OpenEx(serial, OPEN_BY_SERIAL_NUMBER, byref(handle)) == OK:
        return Handle(handle.value)
    raise D2XXException

class Handle(object):
    def __init__(self, handle):
        self.handle = handle
    ...
    def read(self, bytes):
        buffer = create_string_buffer(bytes)
        count = c_int()
        if d2xx.FT_Read(self.handle, buffer, bytes, byref(count)) == OK:
            return buffer.raw[:count.value]
        raise D2XXException
    def write(self, data):
        buffer = create_string_buffer(data)
        count = c_int()
        bytes = len(data)
        if d2xx.FT_Write(self.handle, buffer, bytes, byref(count)) == OK:
            return count.value
        raise D2XXException

Quelqu'un a fait quelques repères sur les différentes options.

Je pourrais être plus hésitant si je devais emballer une bibliothèque C++ avec beaucoup de classes/templates/etc. Mais ctypes fonctionne bien avec les structures et peut même rappel en Python.

109
FogleBird

Attention: l'opinion d'un développeur Cython est en avance.

Je recommande presque toujours Cython sur les ctypes. La raison en est que le chemin de mise à niveau est beaucoup plus fluide. Si vous utilisez des ctypes, beaucoup de choses seront simples au début, et c’est bien d’écrire votre code FFI en Python simple, sans compilation, sans dépendances de construction, etc. Cependant, à un moment donné, vous constaterez presque certainement que vous devez appeler beaucoup dans votre bibliothèque C, soit en boucle, soit dans une série d'appels interdépendants, et vous souhaitez accélérer le processus. C'est le point où vous remarquerez que vous ne pouvez pas faire cela avec les types. 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 également vers le C. Encore une fois, vous ne pouvez pas faire cela avec des ctypes. Vous devez donc changer de langue à ce moment-là et commencer à réécrire des parties de votre code, éventuellement en procédant au reverse engineering de votre code Python/ctypes, ce qui annule l’avantage principal de l’écriture de votre code en clair Python.

Avec Cython, OTOH, vous êtes totalement libre de rendre le code d’emballage et d’appel aussi fin ou plus épais que vous le souhaitez. Vous pouvez commencer par des appels simples dans votre code C à partir de code Python standard, et Cython les traduira en appels C natifs, sans surcharge d'appels supplémentaire et avec une charge 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 passez 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 dans C toi. Vous pouvez également commencer à réécrire des parties de votre code C en Cython afin d’éviter les appels, de spécialiser et de resserrer vos boucles de manière algorithmique. Et si vous avez besoin d’un rappel rapide, écrivez simplement une fonction avec la signature appropriée et transmettez-la directement dans le registre de rappel C. Encore une fois, pas de frais généraux, et cela vous donne de meilleures performances d’appel en C. 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 de l'appeler à partir de votre code Cython de manière naturelle et native. Mais alors, cela devient vraiment le dernier recours au lieu de la seule option.

Donc ctypes est agréable pour faire des choses simples et faire fonctionner rapidement quelque chose. Cependant, dès que la situation commence à prendre de l'ampleur, vous constaterez probablement que vous feriez mieux d'utiliser Cython dès le début.

144
Stefan Behnel

Cython est un outil plutôt cool en soi, qui vaut la peine d'être appris, et qui est étonnamment proche de la syntaxe Python. Si vous faites de l'informatique scientifique avec Numpy, alors Cython est la voie à suivre car elle intègre avec Numpy pour des opérations matricielles rapides.

Cython est un sur-ensemble de la langue Python. Vous pouvez envoyer n'importe quel fichier Python valide, et il créera un programme C valide. Dans ce cas, Cython mappera simplement les appels Python à l'API CPython sous-jacente. Cela peut entraîner une accélération de 50%, car votre code n'est plus interprété.

Pour obtenir certaines optimisations, vous devez commencer à informer Cython de faits supplémentaires sur votre code, tels que les déclarations de type. Si vous le dites assez, le code peut être réduit à un C. pur, c’est-à-dire qu’une boucle for Python devient une boucle for en C. Ici, vous verrez des gains de vitesse énormes. Vous pouvez lien également vers les programmes C externes ici.

L'utilisation du code Cython est également incroyablement simple. Je pensais que le manuel rend le son difficile. Vous faites littéralement:

$ cython mymodule.pyx
$ gcc [some arguments here] mymodule.c -o mymodule.so

et alors vous pouvez import mymodule dans votre code Python et oubliez complètement qu'il compile jusqu'à C.

Dans tous les cas, étant donné que Cython est si facile à installer et à utiliser, je vous suggère de l'essayer pour voir si cela répond à vos besoins. Ce ne sera pas un gaspillage s'il s'avère que ce n'est pas l'outil que vous recherchez.

97
carl

Pour appeler une bibliothèque C depuis une application Python, il y a aussi cffi qui est un nouvelle alternative pour ctypes . Elle apporte un nouveau look à FFI:

  • il traite le problème de manière propre et fascinante (par opposition à ctypes )
  • il ne nécessite pas d'écrire non Python (comme dans SWIG, Cython , ...)
40
Robert Zaremba

Je vais en lancer un autre là-bas: SWIG

Il est facile à apprendre, fait beaucoup de choses correctement et prend en charge un plus grand nombre de langues, de sorte que le temps consacré à son apprentissage peut être très utile.

Si vous utilisez SWIG, vous créez un nouveau module d’extension python), mais SWIG fait le gros du travail pour vous.

21
Chris Arguin

Personnellement, j'écrirais un module d'extension en C. Ne soyez pas intimidé 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 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.

18
mipadi

ctypes est génial quand vous avez déjà un blob de bibliothèque compilé à traiter (comme des bibliothèques de système d'exploitation). Les frais d’appel sont importants, cependant, donc si vous passez beaucoup d’appels dans la bibliothèque et que vous écrivez quand même le code C (ou au moins le compilez), je vous conseillerais de chercher 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 du code python (les comparaisons de boucles et d’entiers sont deux domaines dans lesquels cython brille particulièrement)] Je vais me tourner vers Boost.Python . Boost.Python peut être difficile à configurer, mais une fois que tout fonctionne, le code C/C++ devient plus simple.

cython est également excellent pour emballer numpy (ce que j’ai appris de procédure de SciPy 2009 ), mais je n’ai pas utilisé numpy, je ne peux donc pas en parler.

10
Ryan Ginstrom

Si vous avez déjà une bibliothèque avec une API définie, je pense que ctypes 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 d'un nouveau code, par exemple. appeler cette bibliothèque et effectuer des tâches complexes et fastidieuses, puis transmettre le résultat à Python.

Une autre approche, pour les programmes simples, consiste à exécuter directement un processus différent (compilé en externe), en exportant le résultat vers une sortie standard et en l'appelant avec le module de sous-processus. Parfois, c'est l'approche la plus facile.

Par exemple, si vous créez un programme en console C qui fonctionne plus ou moins de cette façon

$miCcode 10
Result: 12345678

Vous pourriez l'appeler de Python

>>> import subprocess
>>> p = subprocess.Popen(['miCcode', '10'], Shell=True, stdout=subprocess.PIPE)
>>> std_out, std_err = p.communicate()
>>> print std_out
Result: 12345678

Avec un peu de formatage de chaîne, vous pouvez prendre le résultat comme vous le souhaitez. Vous pouvez également capturer la sortie d'erreur standard, ce qui la rend assez flexible.

9
Khelben

Il y a un problème qui m'a incité à utiliser des types et non pas Cython et qui n'est pas mentionné dans d'autres réponses.

En utilisant ctypes, le résultat ne dépend pas du compilateur que vous utilisez. Vous pouvez écrire une bibliothèque en utilisant plus ou moins n'importe quel langage pouvant être compilé 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 voulez utiliser le compilateur Intel sous 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é.

6
Misha

Si vous ciblez Windows et choisissez d’emballer des bibliothèques propriétaires C++, 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 utiliser Cython car il en résulte wrapper.pyd est lié à msvcr90.dll (Python 2.7) ou msvcr100.dll (Python 3.x) . Si la bibliothèque que vous recouvrez est liée à une version différente du runtime, vous n'avez pas de chance.

Ensuite, pour que les choses fonctionnent, vous devez créer des wrappers C pour les bibliothèques C++, liez cette dll wrapper à la même version de msvcrt***.dll comme votre bibliothèque C++. Ensuite, utilisez ctypes pour charger dynamiquement votre DLL de wrapper roulée à la main au moment de l'exécution.

Donc, il y a 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- bibliothèques natives /

3
iljau

Il existe également une possibilité d'utilisation de GObject Introspection pour les bibliothèques utilisant GLib .

2
plaes