Ich möchte eine C-Bibliothek aus einer Python-Anwendung aufrufen. Ich möchte nicht die gesamte API einschließen, sondern nur die Funktionen und Datentypen, die für meinen Fall relevant sind. Aus meiner Sicht habe ich drei Möglichkeiten:
- Erstellen Sie ein tatsächliches Erweiterungsmodul in C. Wahrscheinlich übertrieben, und ich möchte auch den Aufwand für das Erlernen des Schreibens von Erweiterungen vermeiden.
- Verwenden Sie Cython , um die relevanten Teile aus der C-Bibliothek für Python verfügbar zu machen.
- Machen Sie das Ganze in Python und
ctypes
kommunizieren Sie mit der externen Bibliothek.
Ich bin mir nicht sicher, ob 2) oder 3) die bessere Wahl ist. Der Vorteil von 3) ist, dass ctypes
es Teil der Standardbibliothek ist und der resultierende Code reines Python wäre - obwohl ich nicht sicher bin, wie groß dieser Vorteil tatsächlich ist.
Gibt es bei beiden Möglichkeiten weitere Vor- und Nachteile? Welchen Ansatz empfehlen Sie?
Bearbeiten: Vielen Dank für all Ihre Antworten, sie bieten eine gute Ressource für alle, die etwas Ähnliches tun möchten. Die Entscheidung muss natürlich noch für den Einzelfall getroffen werden - es gibt keine Antwort auf die Frage "Das ist das Richtige". Für meinen eigenen Fall werde ich wahrscheinlich mit ctypes arbeiten, aber ich freue mich auch darauf, Cython in einem anderen Projekt auszuprobieren.
Da es keine einzige wahre Antwort gibt, ist es etwas willkürlich, eine zu akzeptieren. Ich habe mich für die Antwort von FogleBird entschieden, da sie einen guten Einblick in ctypes bietet und derzeit auch die Antwort mit der höchsten Bewertung ist. Ich empfehle jedoch, alle Antworten zu lesen, um einen guten Überblick zu erhalten.
Danke noch einmal.
Antworten:
ctypes
ist die beste Wahl, um es schnell zu erledigen, und es ist eine Freude, mit Ihnen zu arbeiten, während Sie noch Python schreiben!Ich habe kürzlich einen FTDI- Treiber für die Kommunikation mit einem USB-Chip über ctypes eingepackt und es war großartig. Ich hatte alles erledigt und arbeitete in weniger als einem Arbeitstag. (Ich habe nur die Funktionen implementiert, die wir brauchten, ungefähr 15 Funktionen).
Wir haben zuvor ein Drittanbieter-Modul, PyUSB , für denselben Zweck verwendet. PyUSB ist ein aktuelles C / Python-Erweiterungsmodul. Aber PyUSB hat die GIL nicht freigegeben, als Lese- / Schreibvorgänge blockiert wurden, was uns Probleme bereitete. Also habe ich unser eigenes Modul mit ctypes geschrieben, das die GIL beim Aufrufen der nativen Funktionen freigibt.
Beachten Sie, dass ctypes nicht über
#define
Konstanten und Inhalte in der von Ihnen verwendeten Bibliothek Bescheid weiß, sondern nur über die Funktionen. Daher müssen Sie diese Konstanten in Ihrem eigenen Code neu definieren.Hier ist ein Beispiel dafür, wie der Code letztendlich aussah (viele wurden herausgeschnitten und versuchten nur, Ihnen das Wesentliche zu zeigen):
Jemand hat einige Benchmarks für die verschiedenen Optionen durchgeführt.
Ich könnte zögerlicher sein, wenn ich eine C ++ - Bibliothek mit vielen Klassen / Vorlagen / etc. Umschließen müsste. Aber ctypes funktioniert gut mit structs und kann sogar Rückruf in Python.
quelle
ctypes
(fürpyinotify
) verwendet, aber ich möchte das Problem genauer verstehen.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.
Also muss ich Konstanten definieren, die inwinioctl.h
...ctypes
ist viel langsamer als die C-Erweiterung, da der Engpass die Schnittstelle von Python zu C istWarnung: Die Meinung eines Cython-Kernentwicklers liegt vor uns.
Ich empfehle Cython fast immer gegenüber ctypes. Der Grund ist, dass es einen viel reibungsloseren Upgrade-Pfad gibt. Wenn Sie ctypes verwenden, werden viele Dinge zunächst einfach sein, und es ist sicherlich cool, Ihren FFI-Code in einfachem Python zu schreiben, ohne zu kompilieren, Abhängigkeiten zu erstellen und so weiter. Irgendwann werden Sie jedoch mit ziemlicher Sicherheit feststellen, dass Sie häufig in Ihrer C-Bibliothek aufrufen müssen, entweder in einer Schleife oder in einer längeren Reihe von voneinander abhängigen Aufrufen, und Sie möchten dies beschleunigen. An diesem Punkt werden Sie feststellen, dass Sie dies mit ctypes nicht tun können. Wenn Sie Rückruffunktionen benötigen und feststellen, dass Ihr Python-Rückrufcode zu einem Engpass wird, möchten Sie ihn beschleunigen und / oder auch nach C verschieben. Auch dies können Sie mit ctypes nicht tun.
Mit Cython, OTOH, können Sie den Umbruch- und Aufrufcode so dünn oder dick gestalten, wie Sie möchten. Sie können mit einfachen Aufrufen Ihres C-Codes aus normalem Python-Code beginnen, und Cython übersetzt sie in native C-Aufrufe, ohne zusätzlichen Aufrufaufwand und mit einem äußerst geringen Konvertierungsaufwand für Python-Parameter. Wenn Sie feststellen, dass Sie an einem Punkt, an dem Sie zu viele teure Aufrufe in Ihre C-Bibliothek tätigen, noch mehr Leistung benötigen, können Sie Ihren umgebenden Python-Code mit statischen Typen versehen und Cython ihn direkt in C für Sie optimieren lassen. Sie können auch Teile Ihres C-Codes in Cython neu schreiben, um Aufrufe zu vermeiden und Ihre Schleifen algorithmisch zu spezialisieren und zu straffen. Und wenn Sie einen schnellen Rückruf benötigen, Schreiben Sie einfach eine Funktion mit der entsprechenden Signatur und übergeben Sie sie direkt an die C-Rückrufregistrierung. Auch hier kein Overhead, und Sie erhalten eine einfache C-Anrufleistung. Und in dem viel weniger wahrscheinlichen Fall, dass Sie Ihren Code in Cython wirklich nicht schnell genug erhalten können, können Sie dennoch in Betracht ziehen, die wirklich kritischen Teile davon in C (oder C ++ oder Fortran) neu zu schreiben und ihn natürlich und nativ aus Ihrem Cython-Code aufzurufen. Aber dann wird dies wirklich der letzte Ausweg statt der einzigen Option.
Ctypes ist also nett, einfache Dinge zu tun und schnell etwas zum Laufen zu bringen. Sobald die Dinge jedoch anfangen zu wachsen, werden Sie höchstwahrscheinlich an den Punkt kommen, an dem Sie feststellen, dass Sie Cython von Anfang an besser verwenden sollten.
quelle
__radd__
. B. ). Dies ist besonders ärgerlich, wenn Sie planen, dass Ihre Klasse mit integrierten Typen (z . B.int
undfloat
) interagiert . Auch magische Methoden in Cython sind im Allgemeinen nur ein bisschen fehlerhaft.Cython ist an sich schon ein ziemlich cooles Tool, das es wert ist, erlernt zu werden, und das der Python-Syntax überraschend nahe kommt. Wenn Sie mit Numpy wissenschaftlich rechnen, ist Cython der richtige Weg, da es für schnelle Matrixoperationen in Numpy integriert ist.
Cython ist eine Obermenge der Python-Sprache. Sie können jede gültige Python-Datei darauf werfen, und es wird ein gültiges C-Programm ausgespuckt. In diesem Fall ordnet Cython die Python-Aufrufe nur der zugrunde liegenden CPython-API zu. Dies führt zu einer Beschleunigung von möglicherweise 50%, da Ihr Code nicht mehr interpretiert wird.
Um einige Optimierungen zu erhalten, müssen Sie Cython zusätzliche Fakten zu Ihrem Code mitteilen, z. B. Typdeklarationen. Wenn Sie es genug sagen, kann es den Code auf reines C reduzieren. Das heißt, eine for-Schleife in Python wird zu einer for-Schleife in C. Hier sehen Sie massive Geschwindigkeitsgewinne. Sie können hier auch auf externe C-Programme verlinken.
Die Verwendung von Cython-Code ist ebenfalls unglaublich einfach. Ich dachte, das Handbuch macht es schwierig. Sie tun buchstäblich nur:
und dann können Sie
import mymodule
in Ihrem Python-Code ganz vergessen, dass es bis zu C kompiliert.Da Cython so einfach einzurichten und zu verwenden ist, empfehle ich auf jeden Fall, es zu versuchen, um festzustellen, ob es Ihren Anforderungen entspricht. Es wird keine Verschwendung sein, wenn sich herausstellt, dass es nicht das Werkzeug ist, nach dem Sie suchen.
quelle
mymod.pyx
Modul und tun Sie dies,import pyximport; pyximport.install(); import mymod
und die Kompilierung erfolgt hinter den Kulissen.runcython mymodule.pyx
. Und im Gegensatz zu pyximport können Sie es für anspruchsvollere Verknüpfungsaufgaben verwenden. Die einzige Einschränkung ist, dass ich derjenige bin, der die 20 Zeilen Bash dafür geschrieben hat und möglicherweise voreingenommen ist.Zum Aufrufen einer C-Bibliothek aus einer Python-Anwendung gibt es auch cffi , eine neue Alternative für ctypes . Es bringt einen frischen Look für FFI:
quelle
Ich werfe noch einen raus: SWIG
Es ist leicht zu lernen, macht viele Dinge richtig und unterstützt viel mehr Sprachen, so dass die Zeit, die Sie damit verbringen, es zu lernen, ziemlich nützlich sein kann.
Wenn Sie SWIG verwenden, erstellen Sie ein neues Python-Erweiterungsmodul, aber SWIG erledigt den größten Teil des schweren Hebens für Sie.
quelle
Persönlich würde ich ein Erweiterungsmodul in C schreiben. Lassen Sie sich nicht von Python C-Erweiterungen einschüchtern - sie sind überhaupt nicht schwer zu schreiben. Die Dokumentation ist sehr klar und hilfreich. Als ich zum ersten Mal eine C-Erweiterung in Python schrieb, brauchte ich ungefähr eine Stunde, um herauszufinden, wie man eine schreibt - nicht viel Zeit.
quelle
ctypes ist großartig, wenn Sie bereits einen kompilierten Bibliotheks-Blob haben (z. B. Betriebssystembibliotheken). Der Anrufaufwand ist jedoch hoch. Wenn Sie also viele Anrufe in die Bibliothek tätigen und den C-Code trotzdem schreiben (oder zumindest kompilieren), würde ich sagen, dass Sie sich dafür entscheiden sollten Cython . Es ist nicht viel mehr Arbeit und es wird viel schneller und pythonischer sein, die resultierende pyd-Datei zu verwenden.
Ich persönlich neige dazu, Cython für eine schnelle Beschleunigung des Python-Codes zu verwenden (Schleifen und Ganzzahlvergleiche sind zwei Bereiche, in denen Cython besonders gut zur Geltung kommt) . Das Einrichten von Boost.Python kann schwierig sein, aber sobald es funktioniert, ist das Umschließen von C / C ++ - Code unkompliziert.
cython ist auch großartig darin, numpy zu verpacken (was ich aus dem SciPy 2009-Verfahren gelernt habe ), aber ich habe numpy nicht verwendet, daher kann ich das nicht kommentieren.
quelle
Wenn Sie bereits eine Bibliothek mit einer definierten API haben, ist dies meiner Meinung
ctypes
nach die beste Option, da Sie nur eine kleine Initialisierung durchführen und die Bibliothek dann mehr oder weniger so aufrufen müssen, wie Sie es gewohnt sind.Ich denke, Cython oder das Erstellen eines Erweiterungsmoduls in C (was nicht sehr schwierig ist) sind nützlicher, wenn Sie neuen Code benötigen, z. B. diese Bibliothek aufrufen und einige komplexe, zeitaufwändige Aufgaben ausführen und das Ergebnis dann an Python übergeben.
Ein anderer Ansatz für einfache Programme besteht darin, direkt einen anderen Prozess (extern kompiliert) auszuführen, das Ergebnis an die Standardausgabe auszugeben und es mit dem Unterprozessmodul aufzurufen. Manchmal ist es der einfachste Ansatz.
Zum Beispiel, wenn Sie ein Konsolen-C-Programm erstellen, das mehr oder weniger so funktioniert
Man könnte es von Python aus aufrufen
Mit einer kleinen String-Formatierung können Sie das Ergebnis beliebig erfassen. Sie können auch die Standardfehlerausgabe erfassen, sodass sie sehr flexibel ist.
quelle
shell=True
Benutzer vorsichtig sein, wenn der Code für den Zugriff durch andere geöffnet werden soll, da das Aufrufen von Unterprozessen mit leicht zu einem Exploit führen kann, wenn ein Benutzer wirklich eine Shell erhält. Es ist in Ordnung, wenn der Entwickler der einzige Benutzer ist, aber auf der Welt gibt es eine ganze Reihe nerviger Stiche, die nur auf so etwas warten.Es gibt ein Problem, bei dem ich ctypes und nicht cython verwendet habe und das in anderen Antworten nicht erwähnt wird.
Bei Verwendung von ctypes hängt das Ergebnis überhaupt nicht vom verwendeten Compiler ab. Sie können eine Bibliothek mit mehr oder weniger jeder Sprache schreiben, die in eine native gemeinsam genutzte Bibliothek kompiliert werden kann. Es spielt keine Rolle, welches System, welche Sprache und welcher Compiler. Cython ist jedoch durch die Infrastruktur begrenzt. Wenn Sie beispielsweise den Intel-Compiler unter Windows verwenden möchten, ist es viel schwieriger, Cython zum Laufen zu bringen: Sie sollten Cython den Compiler "erklären", etwas mit diesem genauen Compiler neu kompilieren usw. Dies schränkt die Portabilität erheblich ein.
quelle
Wenn Sie auf Windows abzielen und einige proprietäre C ++ - Bibliotheken umschließen, werden Sie möglicherweise bald feststellen, dass verschiedene Versionen von
msvcrt***.dll
(Visual C ++ Runtime) leicht inkompatibel sind.Dies bedeutet, dass Sie möglicherweise nicht verwenden können,
Cython
da das Ergebniswrapper.pyd
mitmsvcr90.dll
(Python 2.7) odermsvcr100.dll
(Python 3.x) verknüpft ist . Wenn die Bibliothek, die Sie umschließen, mit einer anderen Laufzeitversion verknüpft ist, haben Sie kein Glück.Damit dies funktioniert, müssen Sie C-Wrapper für C ++ - Bibliotheken erstellen und diese Wrapper-DLL mit derselben Version
msvcrt***.dll
wie Ihre C ++ - Bibliothek verknüpfen . Und dann verwenden Siectypes
, um Ihre handgerollte Wrapper-DLL zur Laufzeit dynamisch zu laden.Es gibt also viele kleine Details, die im folgenden Artikel ausführlich beschrieben werden:
"Schöne native Bibliotheken (in Python) ": http://lucumr.pocoo.org/2013/8/18/beautiful-native-libraries/
quelle
Es gibt auch eine Möglichkeit, GObject Introspection für Bibliotheken zu verwenden, die GLib verwenden .
quelle
Ich weiß, dass dies eine alte Frage ist, aber diese Sache taucht bei Google auf, wenn Sie nach Dingen wie suchen
ctypes vs cython
, und die meisten Antworten hier stammen von Personen, die bereits über ausreichende Kenntnisse verfügencython
oderc
möglicherweise nicht die tatsächliche Zeit widerspiegeln, die Sie für das Erlernen dieser Aufgaben benötigt haben um Ihre Lösung zu implementieren. Ich bin ein absoluter Anfänger in beiden. Ich habe noch nie berührtcython
und habe sehr wenig Erfahrungc/c++
.In den letzten zwei Tagen suchte ich nach einer Möglichkeit, einen leistungsintensiven Teil meines Codes an etwas Niedrigeres als Python zu delegieren. Ich habe meinen Code sowohl in
ctypes
als auch implementiertCython
, der im Wesentlichen aus zwei einfachen Funktionen bestand.Ich hatte eine riesige String-Liste , die verarbeitet werden musste. Hinweis
list
undstring
. Beide Typen entsprechen nicht perfekt den Typen inc
, da Python-Strings standardmäßig Unicode sind undc
Strings nicht. Listen in Python sind einfach KEINE Arrays von c.Hier ist mein Urteil. Verwenden Sie
cython
. Es lässt sich flüssiger in Python integrieren und ist im Allgemeinen einfacher zu handhaben. Wenn etwas schief geht, wirdctypes
nur ein Segfault ausgelöst, und Sie erhalten zumindestcython
Kompilierungswarnungen mit einem Stack-Trace, wann immer dies möglich ist, und Sie können problemlos ein gültiges Python-Objekt zurückgebencython
.Hier ist ein detaillierter Bericht darüber, wie viel Zeit ich in beide investieren musste, um dieselbe Funktion zu implementieren. Ich habe übrigens sehr wenig C / C ++ programmiert:
C-Typen:
c
Code funktioniert.c
Code neu zu ordnen .c
Code in die eigentliche Codebasis eingesteckt und festgestellt , dassctypes
dies mit demmultiprocessing
Modul nicht gut funktioniert, da sein Handler standardmäßig nicht auswählbar ist.multiprocessing
Modul nicht zu verwenden , und es erneut versucht.c
erzeugte die zweite Funktion in meinem Code Segfaults in meiner Codebasis, obwohl sie meinen Testcode bestand. Nun, dies ist wahrscheinlich meine Schuld daran, dass ich nicht gut mit Randfällen umgegangen bin. Ich suchte nach einer schnellen Lösung.c
Codes zu verwenden, und bei der zweiten oder dritten Iteration der Python-Schleife, die sie verwendet, hatteUnicodeError
ich das Problem, an keiner Stelle ein Byte zu decodieren, obwohl ich alles codiert und decodiert habe explizit.An diesem Punkt entschied ich mich, nach einer Alternative zu suchen und untersuchte
cython
:setuptools
anstelle von verwendet wirddistutils
.setup.py
, um ein kompiliertes Modul in meiner Codebasis zu verwenden.multiprocessing
Version der Codebasis. Es klappt.Für die Aufzeichnung habe ich natürlich nicht den genauen Zeitpunkt meiner Investition gemessen. Es kann durchaus sein, dass meine Wahrnehmung der Zeit aufgrund der mentalen Anstrengung, die erforderlich war, während ich mich mit ctypes befasste, etwas zu aufmerksam war. Aber es sollte das Gefühl vermitteln, mit
cython
und umzugehenctypes
quelle