tutorial - Eine C-Bibliothek in Python einpacken: C, Cython oder Ctypes?




python ctypes dll example (8)

Cython ist ein ziemlich cooles Tool, das es sich lohnt zu lernen, und ist der Python-Syntax erstaunlich nahe. Wenn Sie mit Numpy wissenschaftliches Rechnen betreiben, dann ist Cython der richtige Weg, denn es integriert sich mit Numpy für schnelle Matrixoperationen.

Cython ist eine Obermenge der Python-Sprache. Sie können jede gültige Python-Datei darin ausgeben und ein gültiges C-Programm ausgeben. In diesem Fall ordnet Cython die Python-Aufrufe nur der zugrunde liegenden CPython-API zu. Dies führt möglicherweise zu einer Beschleunigung von 50%, da Ihr Code nicht mehr interpretiert wird.

Um einige Optimierungen zu erhalten, müssen Sie Cython weitere Fakten über Ihren Code mitteilen, z. B. Typdeklarationen. Wenn du es genug sagst, kann es den Code auf reines C herunterkochen. Das heißt, eine for-Schleife in Python wird zu einer for-Schleife in C. Hier wirst du massive Geschwindigkeitsgewinne sehen. 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:

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

und dann können Sie import mymodule in Ihren Python-Code import mymodule und vergessen, dass es komplett auf C kompiliert wird.

In jedem Fall, weil Cython so einfach einzurichten und zu verwenden ist, schlage ich vor, es zu versuchen, um zu sehen, ob es Ihren Bedürfnissen entspricht. Es wird keine Verschwendung sein, wenn es sich herausstellt, dass es nicht das Werkzeug ist, nach dem Sie suchen.

https://code.i-harness.com

Ich möchte eine C-Bibliothek von einer Python-Anwendung aufrufen. Ich möchte nicht die gesamte API einbinden, sondern nur die Funktionen und Datentypen, die für meinen Fall relevant sind. Wie ich es sehe, habe ich drei Möglichkeiten:

  1. Erstellen Sie ein tatsächliches Erweiterungsmodul in C. Wahrscheinlich Overkill, und ich möchte auch den Overhead des Lernens von Erweiterungsschreiben vermeiden.
  2. Verwenden Sie Cython , um die relevanten Teile aus der C-Bibliothek für Python verfügbar zu machen.
  3. Machen Sie das Ganze in Python, indem Sie ctypes , um mit der externen Bibliothek zu kommunizieren.

Ich bin mir nicht sicher, ob 2) oder 3) die bessere Wahl ist. Der Vorteil von 3) ist, dass ctypes Teil der Standardbibliothek ist und der resultierende Code reines Python wäre - obwohl ich mir nicht sicher bin, wie groß dieser Vorteil tatsächlich ist.

Gibt es mehr Vorteile / Nachteile mit beiden Möglichkeiten? Welchen Ansatz empfehlen Sie?

Bearbeiten: Danke für all Ihre Antworten, sie bieten eine gute Quelle für jeden, der etwas Ähnliches machen möchte. Die Entscheidung ist natürlich immer noch für den Einzelfall zu treffen - es gibt niemanden "Das ist die richtige Sache", eine Art Antwort. Für meinen eigenen Fall werde ich wahrscheinlich mit ctypes gehen, aber ich freue mich auch, Cython in einem anderen Projekt auszuprobieren.

Da es keine einzige wahre Antwort gibt, ist das Akzeptieren etwas willkürlich; Ich habe mich für die Antwort von FogleBird entschieden, da sie einen guten Einblick in Ctypes bietet und derzeit auch die bestgewählte Antwort ist. Ich schlage jedoch vor, alle Antworten zu lesen, um einen guten Überblick zu bekommen.

Danke noch einmal.



Ich werfe da noch einen raus: SWIG

Es ist einfach zu lernen, macht eine Menge Dinge richtig und unterstützt viele weitere Sprachen, so dass die Zeit, die damit verbracht wird, zu lernen, ziemlich nützlich sein kann.

Wenn Sie SWIG verwenden, erstellen Sie ein neues Python-Erweiterungsmodul, aber mit SWIG erledigen Sie die meisten schweren Aufgaben für Sie.


Persönlich würde ich ein Erweiterungsmodul in C schreiben. Lassen Sie sich von Python C-Erweiterungen nicht einschüchtern - sie sind nicht schwer zu schreiben. Die Dokumentation ist sehr klar und hilfreich. Als ich zum ersten Mal eine C-Erweiterung in Python geschrieben habe, brauchte ich ungefähr eine Stunde, um herauszufinden, wie man eine schreibt - nicht viel Zeit.


Wenn Sie auf Windows abzielen und einige proprietäre C ++ - Bibliotheken msvcrt***.dll , werden Sie möglicherweise bald feststellen, dass verschiedene Versionen von msvcrt***.dll (Visual C ++ Runtime) leicht inkompatibel sind.

Das bedeutet, dass Sie möglicherweise Cython nicht verwenden können, da das resultierende wrapper.pyd mit msvcr90.dll (Python 2.7) oder msvcr100.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.

Dann müssen Sie, um die Dinge zum msvcrt***.dll zu bringen, C-Wrapper für C ++ - Bibliotheken erstellen und diese Wrapper-DLL mit derselben Version von msvcrt***.dll wie Ihre C ++ - Bibliothek verknüpfen. Verwenden ctypes dann ctypes , um Ihre handgerollte Wrapper-DLL dynamisch zur Laufzeit zu laden.

So gibt es viele kleine Details, die im folgenden Artikel ausführlich beschrieben werden:

"Schöne Native Libraries (in Python) ": http://lucumr.pocoo.org/2013/8/18/beautiful-native-libraries/


Wenn Sie bereits eine Bibliothek mit einer definierten API haben, halte ich ctypes für die beste Option, da Sie nur eine kleine Initialisierung durchführen müssen und dann die Bibliothek mehr oder weniger wie gewohnt aufrufen.

Ich denke, dass Cython oder das Erstellen eines Erweiterungsmoduls in C (das nicht sehr schwierig ist) nützlicher sind, wenn Sie neuen Code benötigen, z. B. diese Bibliothek aufrufen und einige komplexe, zeitraubende Aufgaben ausführen und dann das Ergebnis an Python übergeben.

Ein anderer Ansatz für einfache Programme besteht darin, direkt einen anderen Prozess (extern kompiliert) durchzufü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

$miCcode 10
Result: 12345678

Sie könnten es von Python aus aufrufen

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

Mit einer kleinen String-Formatierung können Sie das Ergebnis beliebig gestalten. Sie können auch die Standardfehlerausgabe erfassen, also ist es ziemlich flexibel.


ctypes ist großartig, wenn Sie bereits einen kompilierten Bibliotheks-Blob haben (z. B. Betriebssystembibliotheken). Der Anruf-Overhead ist jedoch schwerwiegend. Wenn Sie also viele Anrufe in die Bibliothek tätigen und Sie den C-Code trotzdem schreiben (oder zumindest kompilieren), würde ich sagen, dass ich das tun soll Cython . Es ist nicht viel mehr Arbeit, und es wird viel schneller und pythonischer sein, die resultierende pyd-Datei zu verwenden.

Ich persönlich benutze Cython für schnelle Beschleunigung von Python-Code (Schleifen und Integer-Vergleiche sind zwei Bereiche, in denen Cython besonders glänzt), und wenn es noch mehr Code / Wrapping anderer involvierter Bibliotheken gibt, wende ich mich an Boost.Python . Boost.Python kann schwierig zu installieren sein, aber sobald es funktioniert, macht es das Wrappen von C / C ++ - Code einfacher.

Cython ist auch großartig beim Einwickeln von numpy (was ich aus dem SciPy 2009-Verfahren gelernt habe), aber ich habe Numpy nicht benutzt, daher kann ich dazu nichts sagen.


ctypes ist deine beste ctypes , um es schnell zu erledigen, und es ist eine Freude, damit zu arbeiten, während du noch immer Python schreibst!

Ich habe vor kurzem einen FTDI Treiber für die Kommunikation mit einem USB-Chip mit Ctypes gewickelt und es war großartig. Ich hatte alles in weniger als einem Arbeitstag erledigt. (Ich habe nur die Funktionen implementiert, die wir brauchten, ungefähr 15 Funktionen).

Zu diesem Zweck haben wir zuvor das Modul PyUSB verwendet. PyUSB ist ein aktuelles C / Python-Erweiterungsmodul. Aber PyUSB hat die GIL nicht freigegeben, als er Lese- / Schreibvorgänge blockierte, was uns Probleme bereitete. Also habe ich mit ctypes ein eigenes Modul geschrieben, das beim Aufruf der nativen Funktionen die GIL freigibt.

Eine Sache zu beachten ist, dass Ctypes nicht wissen #define Konstanten und Sachen in der Bibliothek, die Sie verwenden, nur die Funktionen, so müssen Sie diese Konstanten in Ihrem eigenen Code neu definieren.

Hier ist ein Beispiel dafür, wie der Code letztendlich aussah (viele wurden herausgefiltert, nur um dir den Kern davon zu zeigen):

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

Jemand hat einige Benchmarks für die verschiedenen Optionen gemacht.

Ich könnte zögern, wenn ich eine C ++ Bibliothek mit vielen Klassen / Vorlagen / etc. Aber Ctypes funktioniert gut mit Strukturen und kann sogar in Python zurückrufen.





cython