cythonize - python to cython
Envolviendo una biblioteca de C en Python: C, Cython o ctypes? (11)
Advertencia: la opinión de un desarrollador de Cython Core por delante.
Casi siempre recomiendo Cython sobre ctypes. La razón es que tiene una ruta de actualización mucho más suave. Si usa ctypes, muchas cosas serán simples al principio, y ciertamente es genial escribir su código FFI en Python simple, sin compilación, dependencias de compilación y todo eso. Sin embargo, en algún momento, es casi seguro que tendrá que llamar mucho a su biblioteca de C, ya sea en un bucle o en una serie más larga de llamadas interdependientes, y le gustaría acelerar eso. Ese es el punto en el que notará que no puede hacer eso con ctypes. O bien, cuando necesite funciones de devolución de llamada y descubra que su código de devolución de llamada de Python se convierte en un cuello de botella, le gustaría acelerarlo y / o moverlo a C también. Nuevamente, no puedes hacer eso con ctypes. Por lo tanto, tiene que cambiar los idiomas en ese punto y comenzar a reescribir partes de su código, lo que podría generar ingeniería inversa de su código Python / ctypes en C simple, lo que arruina todo el beneficio de escribir su código en Python simple en primer lugar.
Con Cython, OTOH, eres completamente libre de hacer que el código de ajuste y de llamada sea tan fino o grueso como quieras. Puede comenzar con llamadas simples a su código C desde el código regular de Python, y Cython las convertirá en llamadas C nativas, sin gastos generales de llamadas adicionales, y con una sobrecarga de conversión extremadamente baja para los parámetros de Python. Cuando note que necesita aún más rendimiento en algún momento en el que está realizando demasiadas llamadas costosas a su biblioteca de C, puede comenzar a anotar su código Python circundante con tipos estáticos y dejar que Cython lo optimice directamente en C para usted. O bien, puede comenzar a reescribir partes de su código C en Cython para evitar llamadas y especializarse y ajustar sus bucles algorítmicamente. Y si necesita una devolución de llamada rápida, simplemente escriba una función con la firma apropiada y pásela directamente en el registro de devolución de llamada en C. Una vez más, no hay gastos generales, y le da un rendimiento de llamadas C simple. Y en el caso mucho menos probable de que realmente no pueda obtener su código lo suficientemente rápido en Cython, aún puede considerar reescribir las partes verdaderamente críticas de él en C (o C ++ o Fortran) y llamar desde su código de Cython de forma natural y nativa. Pero entonces, esto realmente se convierte en el último recurso en lugar de la única opción.
Entonces, ctypes es bueno hacer cosas simples y hacer que algo se ejecute rápidamente. Sin embargo, tan pronto como las cosas empiecen a crecer, lo más probable es que llegues al punto en que te des cuenta de que es mejor que uses Cython desde el principio.
Quiero llamar a una biblioteca de C desde una aplicación de Python. No quiero envolver toda la API, solo las funciones y tipos de datos que son relevantes para mi caso. Como lo veo, tengo tres opciones:
- Cree un módulo de extensión real en C. Probablemente sea excesivo, y también me gustaría evitar la sobrecarga de aprender a escribir extensiones.
- Use Cython para exponer las partes relevantes de la biblioteca C a Python.
- Hazlo todo en Python, usando
ctypes
para comunicarte con la biblioteca externa.
No estoy seguro de si 2) o 3) es la mejor opción. La ventaja de 3) es que ctypes
es parte de la biblioteca estándar, y el código resultante sería Python puro, aunque no estoy seguro de cuán grande es esa ventaja.
¿Hay más ventajas / desventajas con cualquiera de las dos opciones? ¿Qué enfoque me recomienda?
Edit: Gracias por todas sus respuestas, proporcionan un buen recurso para cualquiera que busque hacer algo similar. La decisión, por supuesto, aún debe tomarse para el caso individual, no hay una respuesta de tipo "Esto es lo correcto". Para mi propio caso, probablemente iré con ctypes, pero también estoy ansioso por probar Cython en algún otro proyecto.
Dado que no hay una sola respuesta verdadera, aceptar una es algo arbitrario; Elegí la respuesta de FogleBird ya que proporciona una buena perspectiva de los tipos de virus y actualmente también es la respuesta más votada. Sin embargo, sugiero leer todas las respuestas para obtener una buena visión general.
Gracias de nuevo.
Cython es una herramienta muy buena en sí misma, bien vale la pena aprender, y está sorprendentemente cerca de la sintaxis de Python. Si realiza cualquier cálculo científico con Numpy, Cython es el camino a seguir, ya que se integra con Numpy para operaciones matriciales rápidas.
Cython es un superconjunto del lenguaje Python. Puede lanzar cualquier archivo Python válido y escupirá un programa C válido. En este caso, Cython solo asignará las llamadas de Python a la API de CPython subyacente. Esto se traduce en una aceleración del 50% debido a que su código ya no se interpreta.
Para obtener algunas optimizaciones, debe comenzar a contarle a Cython datos adicionales sobre su código, como declaraciones de tipo. Si le dices lo suficiente, puede reducir el código a C pura. Es decir, un bucle for en Python se convierte en un bucle for en C. Aquí verás ganancias masivas de velocidad. También puede enlazar a programas externos de C aquí.
Usar el código Cython también es increíblemente fácil. Pensé que el manual hace que suene difícil. Literalmente solo haces:
$ cython mymodule.pyx
$ gcc [some arguments here] mymodule.c -o mymodule.so
y luego puedes import mymodule
en tu código de Python y olvidarte por completo que se compila hasta C.
En cualquier caso, dado que Cython es muy fácil de configurar y comenzar a usar, sugiero que lo intente para ver si se ajusta a sus necesidades. No será un desperdicio si resulta que no es la herramienta que está buscando.
Hay un problema que me hizo usar ctypes y no cython y que no se menciona en otras respuestas.
Usando ctypes, el resultado no depende en absoluto del compilador que esté usando. Puede escribir una biblioteca utilizando más o menos cualquier idioma que pueda compilarse en una biblioteca compartida nativa. No importa mucho, qué sistema, qué idioma y qué compilador. Cython, sin embargo, está limitado por la infraestructura. Por ejemplo, si desea utilizar el compilador de Intel en Windows, es mucho más complicado hacer que funcione cython: debe "explicar" el compilador a cython, recompilar algo con este compilador exacto, etc. Lo que limita significativamente la portabilidad.
Para llamar a una biblioteca de C desde una aplicación Python también hay cffi que es una nueva alternativa para ctypes . Trae un aspecto fresco para FFI:
- maneja el problema de una manera fascinante y limpia (a diferencia de los ctypes )
- no requiere escribir código no Python (como en SWIG, Cython , ...)
Personalmente, escribiría un módulo de extensión en C. No se deje intimidar por las extensiones de Python C: no son difíciles de escribir. La documentación es muy clara y útil. Cuando escribí por primera vez una extensión C en Python, creo que me tomó aproximadamente una hora descubrir cómo escribir una, no mucho tiempo.
Si está apuntando a Windows y elige envolver algunas bibliotecas propietarias de C ++, pronto podrá descubrir que las diferentes versiones de msvcrt***.dll
(Visual C ++ Runtime) son ligeramente incompatibles.
Esto significa que es posible que no pueda usar Cython
ya que el wrapper.pyd
resultante está vinculado contra msvcr90.dll
(Python 2.7) o msvcr100.dll
(Python 3.x) . Si la biblioteca que está envolviendo está vinculada a una versión diferente del tiempo de ejecución, no tendrá suerte.
Luego, para hacer que las cosas funcionen, deberá crear envoltorios de C para las bibliotecas de C ++, vincular esa dll de envoltura con la misma versión de msvcrt***.dll
que su biblioteca de C ++. Y luego use ctypes
para cargar su dll de envoltura enrollada a mano dinámicamente en el tiempo de ejecución.
Así que hay muchos pequeños detalles, que se describen con gran detalle en el siguiente artículo:
"Bibliotecas nativas hermosas (en Python) ": http://lucumr.pocoo.org/2013/8/18/beautiful-native-libraries/
Si ya tiene una biblioteca con una API definida, creo que ctypes
es la mejor opción, ya que solo tiene que hacer un poco de inicialización y luego más o menos llamar a la biblioteca de la forma en que está acostumbrado.
Creo que Cython o la creación de un módulo de extensión en C (que no es muy difícil) son más útiles cuando necesitas un nuevo código, por ejemplo, llamar a esa biblioteca y hacer algunas tareas complejas que requieren mucho tiempo, y luego pasar el resultado a Python.
Otro enfoque, para programas simples, es hacer directamente un proceso diferente (compilado externamente), emitir el resultado a la salida estándar y llamarlo con el módulo de subproceso. A veces es el enfoque más fácil.
Por ejemplo, si creas un programa de consola C que funcione más o menos de esa manera
$miCcode 10
Result: 12345678
Podrías llamarlo desde 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 poco de formación de cadena, puede tomar el resultado de la forma que desee. También puede capturar la salida de error estándar, por lo que es bastante flexible.
También hay una posibilidad de usar GObject Introspection para las bibliotecas que usan GLib .
Voy a tirar otro por ahí: SWIG
Es fácil de aprender, hace muchas cosas bien y es compatible con muchos más idiomas, por lo que el tiempo empleado en aprenderlo puede ser bastante útil.
Si usas SWIG, estás creando un nuevo módulo de extensión de python, pero con SWIG haciendo la mayor parte del trabajo pesado por ti.
ctypes es genial cuando ya tienes un blob de biblioteca compilado con el que lidiar (como las bibliotecas del sistema operativo). Sin embargo, la sobrecarga de llamadas es grave, por lo que si va a hacer muchas llamadas a la biblioteca y de todos modos va a escribir el código C (o al menos compilarlo), diría que vaya a por Cython . No es mucho más trabajo, y será mucho más rápido y más pitón utilizar el archivo pyd resultante.
Personalmente tiendo a usar cython para acelerar rápidamente el código de Python (los bucles y las comparaciones de enteros son dos áreas en las que cython brilla particularmente), y cuando exista algo más de código / ajuste de otras bibliotecas involucradas, recurriré a Boost.Python . Boost.Python puede ser complicado de configurar, pero una vez que lo tienes funcionando, simplifica el ajuste del código C / C ++.
cython también es genial para envolver numpy (que aprendí de los procedimientos de SciPy 2009 ), pero no he usado numpy, así que no puedo comentar sobre eso.
ctypes
es su mejor apuesta para hacerlo rápidamente, ¡y es un placer trabajar con él mientras aún está escribiendo Python!
Recientemente envolví un controlador FTDI para comunicarme con un chip USB usando ctypes y fue genial. Lo hice todo y trabajé en menos de un día laboral. (Solo implementé las funciones que necesitábamos, unas 15 funciones).
Anteriormente PyUSB módulo de terceros, PyUSB , para el mismo propósito. PyUSB es un módulo de extensión de C / Python real. Pero PyUSB no estaba lanzando el GIL cuando hacía bloqueos de lecturas / escrituras, lo que nos estaba causando problemas. Así que escribí nuestro propio módulo usando ctypes, que libera GIL al llamar a las funciones nativas.
Una cosa a tener en cuenta es que los ctypes no sabrán sobre las constantes #define
y las cosas en la biblioteca que está usando, solo las funciones, por lo que tendrá que redefinir esas constantes en su propio código.
Aquí hay un ejemplo de cómo terminó el código (muchos se cortaron, solo tratando de mostrarte lo esencial):
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
Alguien hizo algunos puntos de referencia en las diversas opciones.
Podría dudar más si tuviera que envolver una biblioteca de C ++ con muchas clases / plantillas / etc. Pero los ctypes funcionan bien con estructuras y pueden incluso volver a llamar a Python.