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


284

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:

  1. 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.
  2. Utilisez Cython pour exposer les parties pertinentes de la bibliothèque C à Python.
  3. Faites le tout en Python, en utilisant ctypespour 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 ctypesfait 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.


3
Dans une certaine mesure, l'application spécifique impliquée (ce que fait la bibliothèque) peut affecter le choix de l'approche. Nous avons utilisé les ctypes avec succès pour parler aux DLL fournies par le fournisseur pour divers composants matériels (par exemple, les oscilloscopes), mais je ne choisirais pas nécessairement les ctypes en premier pour parler à une bibliothèque de traitement numérique, en raison de la surcharge supplémentaire par rapport à Cython ou SWIG.
Peter Hansen

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

@ralu C'est ce que je pense aussi :-) Mais sérieusement, je ne m'attendais pas (ou je ne voulais pas) à une table pour / contre ou à une seule réponse disant "Voici ce que vous devez faire". Il est préférable de répondre à toute question sur la prise de décision avec des "fans" de chaque choix possible en donnant leurs raisons. Le vote communautaire fait alors sa part, tout comme mon propre travail (regarder les arguments, les appliquer à mon cas, lire les sources fournies, etc.). Pour faire court: il y a de bonnes réponses ici.
balpha

Alors, quelle approche allez-vous suivre? :)
FogleBird

1
Pour autant que je sache (veuillez me corriger si je me trompe), Cython est un fork de Pyrex avec plus de développement, ce qui rend Pyrex à peu près obsolète.
balpha

Réponses:


115

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 #defineconstantes 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):

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 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.


5
Rejoindre les louanges pour les ctypes, mais notez un problème (non documenté): ctypes ne prend pas en charge le forking. Si vous effectuez un fork à partir d'un processus utilisant des ctypes et que les processus parents et enfants continuent à utiliser des ctypes, vous tomberez sur un bogue désagréable lié aux ctypes utilisant la mémoire partagée.
Oren Shemesh

1
@OrenShemesh Y a-t-il d'autres lectures sur ce problème que vous pouvez me signaler? Je pense que je peux être en sécurité avec un projet sur lequel je travaille actuellement, car je crois que seul le processus parent utilise ctypes(pour pyinotify), mais j'aimerais mieux comprendre le problème.
zigg

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 les constantes qui sont là dans winioctl.h....
swdev

qu'en est-il de la performance? ctypesest beaucoup plus lent que l'extension c car le goulot d'étranglement est l'interface de Python à C
TomSawyer

154

Attention: 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.


4
+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 est vraiment une telle surcharge. Mais je suis d'accord, si vous vous attendez à des problèmes de performances, vous pouvez tout aussi bien utiliser Cython depuis le début.
balpha

Cela vaut-il toujours pour les programmeurs expérimentés avec C et Python? Dans ce cas, on peut affirmer que Python / ctypes est le meilleur choix, car la vectorisation des boucles C (SIMD) est parfois plus simple. Mais, à part cela, je ne peux penser à aucun inconvénient de Cython.
Alex van Houten

Merci d'avoir répondu! Une chose que j'ai eu du mal avec Cython est d'obtenir le bon processus de construction (mais cela a aussi à voir avec moi n'ayant jamais écrit de module Python auparavant) - devrais-je le compiler avant, ou inclure des fichiers source Cython dans sdist et des questions similaires. J'ai écrit un blog à ce sujet au cas où quelqu'un aurait des problèmes / doutes similaires: martinsosic.com/development/2016/02/08/…
Martinsos

Merci d'avoir répondu! Un inconvénient lorsque j'utilise Cython est que la surcharge de l'opérateur n'est pas complètement implémentée (par exemple __radd__). C'est particulièrement gênant lorsque vous prévoyez que votre classe interagisse avec les types intégrés (par exemple, intet float). De plus, les méthodes magiques dans cython sont juste un peu boguées en général.
Monolith

100

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:

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

puis vous pouvez import mymoduledans 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.


1
Aucun problème. La bonne chose à propos de Cython est que vous ne pouvez apprendre que ce dont vous avez besoin. Si vous voulez seulement une amélioration modeste, tout ce que vous avez à faire est de compiler vos fichiers Python et vous avez terminé.
carl

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

7
Les problèmes diminuent avec chaque version, au point que cette page indique désormais "la plupart des problèmes ont été résolus en 0.15".
Henry Gomersall

3
Pour ajouter, il existe un moyen encore plus simple d'importer du code cython: écrivez votre code cython en tant que mymod.pyxmodule, puis faites import pyximport; pyximport.install(); import mymod-le et la compilation se déroule en arrière-plan.
Kaushik Ghose

3
@kaushik Encore plus simple est pypi.python.org/pypi/runcython . Utilisez simplement 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é.
RussellStewart

42

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:

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

certainement la voie à suivre pour l' emballage , comme OP le voulait. cython sonne bien pour les écrire vous-même, mais pour les interfaces, cffi est simplement une mise à niveau directe de ctypes.
vol de moutons

21

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.


18

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.


Envelopper une bibliothèque C. Vous pouvez réellement trouver le code ici: github.com/mdippery/lehmer
mipadi

1
@forivall: Le code n'était pas vraiment très utile, et il existe de meilleurs générateurs de nombres aléatoires. Je n'ai qu'une sauvegarde sur mon ordinateur.
mipadi

2
D'accord. La C-API de Python n'est pas aussi effrayante qu'elle en a l'air (en supposant que vous connaissez C). Cependant, contrairement à python et à son réservoir de bibliothèques, de ressources et de développeurs, lorsque vous écrivez des extensions en C, vous êtes essentiellement seul. Probablement son seul inconvénient (autres que ceux qui accompagnent généralement l'écriture en C).
Noob Saibot

1
@mipadi: bien, mais ils diffèrent entre Python 2.x et 3.x, il est donc plus pratique d'utiliser Cython pour écrire votre extension, demandez à Cython de comprendre tous les détails, puis compilez le code C généré pour Python 2.x ou 3.x au besoin.
0xC0000022L

2
@mipadi il semble que le lien github soit mort et il ne semble pas disponible sur archive.org, avez-vous une sauvegarde?
HJR

11

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.


11

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

$miCcode 10
Result: 12345678

Vous pourriez l'appeler depuis 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 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.


Bien qu'il n'y ait rien de mal à cette réponse, les gens doivent être prudents si le code doit être ouvert à d'autres personnes car un sous-processus appelant shell=Truepourrait 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.
Ben

7

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é.


4

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 Cythoncar le résultat wrapper.pydest lié à msvcr90.dll (Python 2.7) ou msvcr100.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***.dllque celle de votre bibliothèque C ++. Ensuite, utilisez ctypespour 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/


Cet article n'a rien à voir avec les problèmes que vous évoquez avec la compatibilité des compilateurs Microsoft. Obtenir des extensions Cython fonctionnant sous Windows n'est vraiment pas très difficile. J'ai pu utiliser MinGW pour à peu près tout. Une bonne distribution Python aide cependant.
IanH

2
+1 pour avoir mentionné un problème possible sur Windows (que j'éprouve aussi actuellement ...). @IanH, il s'agit moins de Windows en général, mais c'est un gâchis si vous êtes coincé avec une bibliothèque tierce donnée qui ne correspond pas à votre distribution python.
sebastian


2

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étents cythonou cqui 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é cythonauparavant et j'ai très peu d'expérience c/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 ctypeset Cython, qui consistait essentiellement en deux fonctions simples.

J'avais une énorme liste de chaînes à traiter. Remarquez listet string. Les deux types ne correspondent pas parfaitement aux types de c, car les chaînes python sont par défaut unicode et les cchaî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, ctypesvous lancez une erreur de segmentation, au moins cythonvous 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 avec cython.

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:

    • Environ 2h sur les recherches pour transformer ma liste de chaînes unicode en type compatible AC.
    • Environ une heure sur la façon de renvoyer une chaîne correctement à partir de la fonction ca. Ici, j'ai fourni ma propre solution à SO une fois que j'ai écrit les fonctions.
    • Environ une demi-heure pour écrire le code en c, le compiler dans une bibliothèque dynamique.
    • 10 minutes pour écrire un code de test en python pour vérifier si le ccode fonctionne.
    • Environ une heure pour faire des tests et réorganiser le ccode.
    • Ensuite, j'ai branché le ccode dans la base de code réelle et j'ai vu que ctypescela ne fonctionnait pas bien avec le multiprocessingmodule car son gestionnaire n'était pas sélectionnable par défaut.
    • Environ 20 minutes, j'ai réorganisé mon code pour ne pas utiliser le multiprocessingmodule et réessayé.
    • Ensuite, la deuxième fonction de mon ccode 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.
    • Pendant environ 40 minutes, j'ai essayé de déterminer les causes possibles de ces erreurs de segmentation.
    • J'ai divisé mes fonctions en deux bibliothèques et j'ai réessayé. Il y avait encore des erreurs de segmentation pour ma deuxième fonction.
    • J'ai décidé de lâcher la deuxième fonction et d'utiliser uniquement la première fonction de ccode et à la deuxième ou troisième itération de la boucle python qui l'utilise, j'avais un UnicodeErrorproblè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:

  • Cython
    • 10 min de lecture cython hello world .
    • 15 min de vérification de SO sur la façon d'utiliser cython avec setuptoolsau lieu de distutils.
    • 10 min de lecture sur les types cython et python. J'ai appris que je peux utiliser la plupart des types de python intégrés pour le typage statique.
    • 15 min de réannotation de mon code python avec des types cython.
    • 10 min de modification de mon setup.pypour utiliser le module compilé dans ma base de code.
    • Connecté le module directement à la multiprocessingversion 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 cythonetctypes

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.