Domanda

voglio chiamare una libreria C da un'applicazione Python. Non voglio per avvolgere l'intero API, solo le funzioni e tipi di dati che sono rilevanti per il mio caso. Come la vedo io, ho tre possibilità:

  1. Crea un modulo di estensione reale in C. probabilmente eccessivo, e mi piacerebbe anche per evitare il sovraccarico di imparare la scrittura estensione.
  2. Cython per esporre le parti pertinenti dalla libreria C per Python.
  3. Fare il tutto in Python, utilizzando ctypes per comunicare con la libreria esterna.

Non sono sicuro se 2) o 3) è la scelta migliore. Il vantaggio di 3) è che ctypes fa parte della libreria standard, e il codice risultante sarebbe puro Python -. Anche se non sono sicuro di come grande che vantaggio è in realtà

Ci sono altri vantaggi / svantaggi sia con la scelta? Quale approccio mi consigliate?


Modifica Grazie per tutte le vostre risposte, forniscono una buona risorsa per chi cerca di fare qualcosa di simile. La decisione, naturalmente, è ancora essere fatto per il singolo caso non c'è nessuno "Questa è la cosa giusta" tipo di risposta. Per il mio caso, io probabilmente andare con ctypes, ma sono anche impaziente di provare Cython in qualche altro progetto.

Con che vi sia una risposta unica vera, accettando uno è un po 'arbitraria; Ho scelto la risposta di FogleBird in quanto fornisce un po 'di buona conoscenza ctypes ed attualmente è anche la risposta più alto votato. Tuttavia, vi suggerisco di leggere tutte le risposte per avere una buona visione.

Grazie di nuovo.

È stato utile?

Soluzione

ctypes è la soluzione migliore per ottenere fatto in fretta, ed è un piacere lavorare con, come si sta ancora scrivendo Python!

Recentemente ho avvolto un href="http://www.ftdichip.com/" rel="noreferrer"> FTDI conducente

Siamo stati in precedenza usando un modulo di terze parti, PyUSB , per lo stesso scopo. PyUSB è un modulo di espansione C / Python reale. Ma PyUSB non stava rilasciando la GIL quando si fa il blocco legge / scrive, che stava causando problemi per noi. Così ho scritto il nostro modulo utilizzando ctypes, che fa rilasciare il GIL quando si chiama le funzioni native.

Una cosa da notare è che ctypes non sapranno sulle costanti #define e roba nella libreria che si sta utilizzando, solo le funzioni, in modo da dovrete ridefinire queste costanti nel proprio codice.

Ecco un esempio di come il codice finito cercando (lotti snipped fuori, solo cercando di mostrare l'essenza di esso):

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

Qualcuno ha fatto alcuni benchmark sulle varie opzioni.

potrei essere più esitante se dovessi avvolgere una libreria C ++ con un sacco di classi / templates / etc. Ma ctypes funziona bene con le strutture e può anche callback in Python.

Altri suggerimenti

. Attenzione: il parere di uno sviluppatore nucleo Cython avanti

Vi consiglio quasi sempre Cython sopra ctypes. La ragione è che ha un percorso di aggiornamento molto più agevole. Se si utilizza ctypes, molte cose saranno semplici in un primo momento, ed è certamente fresco per scrivere il codice in Python FFI pianura, senza compilazione, le dipendenze e tutto il resto. Tuttavia, ad un certo punto, sarà quasi certamente trovare che si deve mettere in vostra libreria C molto, sia in un loop o in una serie più lunga di chiamate interdipendenti, e che si desidera velocizzare che fino. Questo è il punto in cui si noterà che non è possibile farlo con ctypes. Oppure, quando avete bisogno di funzioni di callback e si scopre che il codice di callback Python diventa un collo di bottiglia, vuoi per accelerarlo e / o spostare verso il basso in C pure. Anche in questo caso, non è possibile farlo con ctypes. Quindi devi passare da una lingua in quel punto e iniziare a riscrivere parti del codice, potenzialmente reverse engineering il codice / ctypes Python in pianura C, rovinando così l'intero vantaggio di scrivere il codice in pianura Python, in primo luogo.

Con Cython, OTOH, sei completamente libero di fare il confezionamento e codice chiamante come sottile o spessa come si desidera. Si può iniziare con semplici chiamate nel tuo codice C da codice Python regolare, e Cython li traduce in chiamate C nativo, senza alcun sovraccarico chiamata supplementare, e con un overhead di conversione estremamente basso per i parametri di Python. Quando si nota che è necessario ancora di più le prestazioni ad un certo punto in cui si stanno facendo troppe chiamate costose nella libreria C, è possibile avviare l'annotazione il codice Python circostante con tipi statici e lasciare Cython ottimizzare dritto giù in C per voi. In alternativa, è possibile iniziare a riscrivere parti del codice C in Cython al fine di evitare le chiamate e di specializzarsi e stringere i loop algoritmicamente. E se avete bisogno di un callback veloce, basta scrivere una funzione con la firma appropriata e passarlo nel registro C richiamata direttamente. Anche in questo caso, non appesantisce, e ti dà prestazioni pianura C chiamando. E nel caso molto meno probabile che davvero non si può ottenere il codice abbastanza veloce in Cython, si può ancora considerare riscrivere le parti veramente critiche di esso in C (o C ++ o Fortran) e lo chiamano dal codice Cython naturalmente e in modo nativo. Ma poi, questo diventa davvero l'ultima risorsa invece che l'unica opzione.

Quindi, ctypes è bello fare cose semplici e per ottenere rapidamente qualcosa in esecuzione. Tuttavia, non appena le cose cominciano a crescere, che molto probabilmente arriva al punto in cui si nota che è meglio utilizzato Cython fin dall'inizio.

Cython è uno strumento piuttosto fresco in sé, vale la pena di apprendimento, ed è sorprendentemente vicino alla sintassi di Python. Se lo fai qualsiasi calcolo scientifico con Numpy, allora Cython è la strada da percorrere perché si integra con Numpy per le operazioni di matrice veloce.

Cython è un superset del linguaggio Python. Si può gettare qualsiasi tipo di file Python valido a esso, e sarà sputare fuori un programma C valido. In questo caso, Cython si limita a mappare il Python chiama del sottostante API CPython. Questo si traduce in un aumento di velocità, forse il 50%, perché il codice non è più interpretato.

Per ottenere alcune ottimizzazioni, è necessario iniziare a raccontare Cython ulteriori fatti circa il codice, come dichiarazioni di tipo. Se gli si dice abbastanza, si può far bollire il codice fino a C. pura Cioè, un ciclo for in Python diventa un ciclo for in C. Qui potrete vedere i guadagni di velocità enormi. È inoltre possibile collegare a programmi C esterni qui.

Utilizzando il codice Cython è anche incredibilmente facile. Ho pensato che il manuale lo fa sembrare difficile. È letteralmente solo fare:

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

e quindi si può import mymodule nel codice Python e dimenticare del tutto che si compila fino a C.

In ogni caso, a causa Cython è così facile da installare e iniziare a utilizzare, vi consiglio di provarlo per vedere se si adatta alle vostre esigenze. Non sarà uno spreco se si scopre di non essere lo strumento che stai cercando.

Per chiamare una libreria C da un'applicazione Python v'è anche CFFI , che è una nuova alternativa per ctypes . Si porta uno sguardo fresco per FFI:

  • gestisce il problema in un affascinante modo pulito (a differenza di ctypes )
  • non richiede di scrivere codice non Python (come in SWIG, Cython , ...)

mi butto un altro là fuori: SWIG

E 'facile da imparare, fa un sacco di cose giuste, e supporta molte altre lingue in modo che il tempo trascorso apprendimento può essere molto utile.

Se si usa SWIG, si sta creando un nuovo modulo di estensione Python, ma con SWIG fare la maggior parte del lavoro pesante per voi.

Personalmente, mi piacerebbe scrivere un modulo di estensione in C. Non essere intimidito da estensioni Python C - non sono affatto difficile da scrivere. La documentazione è molto chiaro e disponibile. Quando ho scritto un'estensione C in Python, penso che mi ci sono voluti circa un'ora per capire come scrivere uno -. Non molto tempo a tutti

ctypes è grande quando hai già un blob libreria compilata a che fare con ( come ad esempio le biblioteche del sistema operativo). L'overhead chiamando è grave, però, quindi se sarete fare un sacco di chiamate in biblioteca, e si sta andando ad essere la scrittura del codice C in ogni caso (o almeno compilazione), direi di andare per Cython . Non è molto più lavoro, e sarà molto più veloce e più divinatorio di utilizzare il file risultante PYD.

Io personalmente tendo ad usare Cython per incrementi nella velocità veloci di codice python (loop e confronti interi sono due aree in cui Cython brilla particolarmente), e quando c'è qualche più coinvolti codice / avvolgimento di altre biblioteche coinvolte, mi trasformerò a < a href = "http://www.boost.org/doc/libs/1_41_0/libs/python/doc/index.html" rel = "noreferrer"> Boost.Python . Boost.Python può essere pignoli da configurare, ma una volta che ce l'hai di lavoro, rende il codice avvolgimento C / C ++ semplice.

Cython è grande anche al confezionamento NumPy (che ho imparato dal SciPy 2009 procedimenti ), ma non ho usato NumPy, quindi non posso commentare in merito.

Se si dispone già di una biblioteca con un'API definita, penso ctypes è l'opzione migliore, come si deve solo fare un po 'di inizializzazione e quindi più o meno chiamare la libreria il modo in cui siete abituati.

Credo Cython o la creazione di un modulo di estensione in C (che non è molto difficile) sono più utile quando è necessario un nuovo codice, ad esempio, chiamando quella biblioteca e fare un po 'complesse, attività che richiede tempo, e poi passando il risultato a Python.

Un altro approccio, per programmi semplici, è fare direttamente un processo differente (compilato esternamente), in uscita il risultato standard output e chiamarla con modulo sottoprocesso. A volte è l'approccio più semplice.

Ad esempio, se si effettua un programma di console C che funziona più o meno in questo modo

$miCcode 10
Result: 12345678

Si potrebbe chiamare da Python

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

Con un po 'di formattazione stringa, si può prendere il risultato in qualsiasi modo si desidera. È inoltre possibile catturare l'output di errore standard, quindi è molto flessibile.

C'è una questione che mi ha fatto uso ctypes e non Cython e che non è menzionato in altre risposte.

Utilizzando ctypes il risultato non dipende compilatore in uso a tutti. Si può scrivere una libreria con più o meno qualsiasi linguaggio che può essere compilato per libreria condivisa nativa. Non importa molto, quale sistema, che linguaggio e quale compilatore. Cython, tuttavia, è limitata dalle infrastrutture. Per esempio, se si desidera utilizzare il compilatore Intel su Windows, è molto più difficile da far funzionare Cython:. Si dovrebbe "spiegare" compilatore di Cython, ricompilare qualcosa con questo compilatore esatto, ecc da limitare notevolmente la portabilità

Se si prendono di mira Windows e sceglie di avvolgere alcune librerie proprietarie C ++, allora si potrebbe presto scoprire che le diverse versioni di msvcrt***.dll (Visual C ++ Runtime) sono leggermente incompatibili.

Ciò significa che non si può essere in grado di utilizzare Cython in quanto risultante wrapper.pyd è collegata contro msvcr90.dll (Python 2.7) o msvcr100.dll (Python 3.x) . Se la libreria che si sta avvolgendo è legata contro versione diversa di runtime, allora sei fuori di fortuna.

Poi, per far funzionare le cose è necessario creare wrapper C per le librerie C ++, creare un collegamento che dll involucro contro la stessa versione del msvcrt***.dll come la libreria C ++. E quindi utilizzare ctypes per caricare il tuo involucro arrotolato a mano DLL dinamicamente in fase di esecuzione.

Quindi ci sono un sacco di piccoli dettagli, che sono descritti in dettaglio nel seguente articolo:

"Bella Native Biblioteche (in Python) ": http://lucumr.pocoo.org/2013/8/18/beautiful-native-libraries/

C'è anche una possibilità di utilizzare GObject introspezione per le librerie che utilizzano GLib.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top