292 votes

Habillage d'une bibliothèque C en Python : C, Cython ou ctypes ?

Je veux appeler une bibliothèque C à partir d'une application Python. Je ne veux pas envelopper toute l'API, seulement les fonctions et les types de données qui sont pertinents pour mon cas. À mon avis, j'ai trois possibilités :

  1. Créer un module d'extension en C. C'est probablement exagéré, et j'aimerais aussi éviter d'avoir à apprendre à écrire des extensions.
  2. Utilice Cython pour exposer les parties pertinentes de la bibliothèque C à Python.
  3. Faites le 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 le code résultant serait purement Python - bien que je ne sois pas sûr de l'importance de cet avantage.

Y a-t-il plus d'avantages/inconvénients avec l'un ou l'autre des choix ? Quelle approche recommandez-vous ?


Editar: Merci pour toutes vos réponses, elles constituent une bonne ressource pour quiconque cherche à faire quelque chose de similaire. La décision, bien sûr, doit encore être prise pour chaque cas - il n'y a pas de réponse unique du type "C'est la bonne chose à faire". Dans mon cas, j'opterai probablement pour ctypes, mais j'ai également hâte d'essayer Cython dans le cadre d'un autre projet.

Comme il n'y a pas une seule vraie réponse, en choisir une est quelque peu arbitraire ; j'ai choisi la réponse de FogleBird car elle donne un bon aperçu de ctypes et c'est actuellement la réponse la plus votée. Cependant, je suggère de lire toutes les réponses pour avoir une bonne vue d'ensemble.

Merci encore.

4 votes

Dans une certaine mesure, l'application spécifique concernée (ce que fait la bibliothèque) peut affecter le choix de l'approche. Nous avons utilisé ctypes avec beaucoup de succès pour parler à des DLL fournies par des fournisseurs pour diverses pièces de matériel (par exemple des oscilloscopes), mais je ne choisirais pas nécessairement ctypes en premier pour parler à une bibliothèque de traitement numérique, en raison de la surcharge par rapport à Cython ou SWIG.

1 votes

Maintenant vous avez ce que vous cherchiez. Quatre réponses différentes. (quelqu'un a aussi trouvé SWIG). Cela signifie que maintenant vous avez 4 choix au lieu de 3.

0 votes

@ralu C'est ce que je pensais aussi :-) Mais sérieusement, je ne m'attendais pas (ou ne voulais pas) d'un tableau pro/con ou d'une réponse unique disant "Voici ce que vous devez faire". Toute question sur la prise de décision est mieux répondue par des "fans" de chaque choix possible donnant leurs raisons. Le vote de la communauté fait alors sa part, tout comme mon propre travail (examiner les arguments, les appliquer à mon cas, lire les sources fournies, etc.) En bref, il y a de bonnes réponses ici.

162voto

Stefan Behnel Points 511

Avertissement : l'avis d'un développeur de Cython est à venir.

Je recommande presque toujours Cython plutôt que ctypes. La raison en est que le chemin de mise à jour est beaucoup plus facile. Si vous utilisez ctypes, beaucoup de choses seront simples au début, et c'est certainement cool d'écrire votre code FFI en Python, sans compilation, sans dépendances de compilation et tout ça. Cependant, à un moment donné, vous vous rendrez certainement compte que vous devez souvent faire appel à votre bibliothèque C, soit dans une boucle, soit dans une longue série d'appels interdépendants, et vous voudrez accélérer le processus. C'est à ce moment-là que vous remarquerez que vous ne pouvez pas le faire avec 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 aimeriez l'accélérer et/ou le déplacer vers le C également. Encore une fois, vous ne pouvez pas faire cela avec ctypes. Vous devez donc changer de langage à ce moment-là et commencer à réécrire des parties de votre code, ce qui peut entraîner une rétro-ingénierie de votre code Python/ctypes en C, ce qui gâche tout l'avantage d'écrire votre code en Python en premier lieu.

Avec Cython, en revanche, vous êtes totalement libre de rendre le code d'habillage et d'appel aussi fin ou épais que vous le souhaitez. Vous pouvez commencer par de simples appels dans votre code C à partir de code Python ordinaire, et Cython les traduira en appels C natifs, sans aucune 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 d'encore plus de performances à un moment où vous faites 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. Vous pouvez également commencer à réécrire des parties de votre code C en Cython afin d'éviter les appels et de spécialiser et resserrer vos boucles de manière algorithmique. Et si vous avez besoin d'un callback rapide, il vous suffit d'écrire une fonction avec la signature appropriée et de la passer directement dans le registre de callback C. Encore une fois, il n'y a pas de surcharge, et cela vous donne des performances d'appel en C pur. Et dans le cas, beaucoup moins probable, où vous ne parvenez pas à rendre votre code suffisamment rapide en Cython, vous pouvez toujours envisager de réécrire les parties vraiment critiques en C (ou C++ ou Fortran) et de les appeler depuis votre code Cython de manière naturelle et native. Mais dans ce cas, cela devient vraiment le dernier recours et non la seule option.

Ainsi, ctypes est utile pour faire des choses simples et pour faire fonctionner quelque chose rapidement. Cependant, dès que les choses commencent à prendre de l'ampleur, vous arriverez très probablement à un point où vous constaterez qu'il vaut mieux utiliser Cython dès le départ.

4 votes

+1 ce sont de bons points, merci beaucoup ! Bien que je me demande si le fait de déplacer uniquement les parties du goulot d'étranglement vers Cython représente vraiment une telle surcharge. Mais je suis d'accord, si vous vous attendez à des problèmes de performance, vous pourriez aussi bien utiliser Cython dès le début.

1 votes

Est-ce que cela reste valable pour les programmeurs expérimentés à la fois en C et en Python ? Dans ce cas, on peut dire que Python/ctypes est le meilleur choix, car la vectorisation des boucles C (SIMD) est parfois plus simple. Mais, à part cela, je ne vois pas d'inconvénients à Cython.

0 votes

Merci pour la réponse ! Une chose avec laquelle j'ai eu des difficultés concernant Cython est le processus de construction (mais cela a aussi à voir avec le fait que je n'ai jamais écrit de module Python auparavant) - dois-je le compiler avant, ou inclure les fichiers sources Cython dans sdist et d'autres questions similaires. J'ai écrit un billet de blog à ce sujet au cas où quelqu'un aurait des problèmes ou des doutes similaires : martinsosic.com/development/2016/02/08/

120voto

FogleBird Points 23405

ctypes est votre meilleure chance de le faire rapidement, et c'est un plaisir de travailler avec lui pendant que vous écrivez Python !

J'ai récemment emballé un FTDI pour communiquer avec une puce USB en utilisant ctypes et c'était génial. Tout était prêt et fonctionnait en moins d'une journée de travail. (Je n'ai implémenté que les fonctions dont nous avions besoin, soit environ 15 fonctions).

Nous utilisions auparavant un module tiers, PyUSB pour le même objectif. PyUSB est un véritable module d'extension C/Python. Mais PyUSB ne libérait pas la GIL lors des lectures/écritures bloquantes, ce qui nous posait problème. J'ai donc écrit notre propre module en utilisant ctypes, qui libère la GIL lors de l'appel des fonctions natives.

Une chose à noter est que ctypes n'est pas au courant de #define dans la bibliothèque que vous utilisez, seulement les fonctions, donc vous devrez redéfinir ces constantes dans votre propre code.

Voici un exemple de ce à quoi le code a fini par ressembler (beaucoup d'extraits, juste pour vous 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 points de repère sur les différentes options.

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

6 votes

Je me joins aux louanges de ctypes, mais je remarque un problème (non documenté) : ctypes ne supporte pas le forking. Si vous bifurquez d'un processus utilisant ctypes, et que les processus parent et enfant continuent à utiliser ctypes, vous tomberez sur un méchant bogue qui a à voir avec l'utilisation de la mémoire partagée par ctypes.

1 votes

@OrenShemesh Y a-t-il d'autres lectures sur cette question que vous pouvez m'indiquer ? Je pense que je suis peut-être en sécurité avec un projet sur lequel je travaille actuellement, car je crois que seul le processus parent utilise la fonction ctypes (pour pyinotify ), mais j'aimerais comprendre le problème de manière plus approfondie.

0 votes

Ce passage m'aide beaucoup 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 des constantes qui sont là dans winioctl.h ....

101voto

carl Points 25879

Cython est un outil assez cool en soi, qui vaut la peine d'être appris et qui est étonnamment proche de la syntaxe Python. Si vous effectuez des calculs scientifiques avec Numpy, alors Cython est la solution car il s'intègre à Numpy pour des opérations matricielles rapides.

Cython est un sur-ensemble du langage Python. Vous pouvez lui envoyer n'importe quel fichier Python valide, et il vous sortira un programme C valide. Dans ce cas, Cython se contentera de faire correspondre les appels Python à l'API CPython sous-jacente. Il en résulte une accélération d'environ 50 %, car votre code n'est plus interprété.

Pour obtenir certaines optimisations, vous devez commencer à fournir à Cython des informations supplémentaires sur votre code, comme les déclarations de types. Si vous lui en dites suffisamment, il peut ramener le code à du C pur. Autrement dit, une boucle for en Python devient une boucle for en C. Vous constaterez alors des gains de vitesse considérables. Vous pouvez également créer des liens vers des programmes C externes ici.

L'utilisation du code Cython est également incroyablement facile. Le manuel le fait paraître difficile. Vous avez littéralement juste à faire :

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

et ensuite vous pouvez import mymodule dans votre code Python et oubliez complètement qu'il se compile en C.

Quoi qu'il en soit, Cython étant si facile à installer et à utiliser, je vous suggère de l'essayer pour voir s'il répond à vos besoins. Ce ne sera pas un gaspillage s'il s'avère ne pas être l'outil que vous recherchez.

1 votes

Pas de problème. Ce qui est bien avec Cython, c'est que vous ne pouvez apprendre que ce dont vous avez besoin. Si vous ne souhaitez qu'une amélioration modeste, il vous suffit de compiler vos fichiers Python et le tour est joué.

19 votes

"Vous pouvez lui jeter n'importe quel fichier Python valide, et il vous sortira un programme C valide." <-- Pas tout à fait, il y a quelques limitations : docs.cython.org/src/userguide/limitations.html Ce n'est probablement pas un problème pour la plupart des cas d'utilisation, mais je voulais juste être complet.

7 votes

Les problèmes sont de moins en moins nombreux à chaque version, au point que cette page indique maintenant "la plupart des problèmes ont été résolus dans la 0.15".

42voto

Robert Zaremba Points 1925

Pour appeler une bibliothèque C à partir d'une application Python, il y a aussi cffi qui est une nouvelle alternative pour ctypes . Il apporte un regard neuf à FFI :

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

1 votes

C'est définitivement la voie à suivre pour emballage Cython semble idéal pour écrire soi-même les boucles chaudes, mais pour les interfaces, cffi est simplement une mise à niveau directe de ctypes.

21voto

Chris Arguin Points 6469

Je vais en lancer une autre : SWIG

Il est facile à apprendre, fait beaucoup de choses bien, et supporte beaucoup plus de langues, de sorte que 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 se charge de la plupart des tâches à votre place.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X