Quiero llamar a una biblioteca C desde una aplicación Python. No quiero ajustar toda la API, solo las funciones y los tipos de datos que son relevantes para mi caso. Tal como lo veo, tengo tres opciones:
- Cree un módulo de extensión real en C. Probablemente exagere, y también me gustaría evitar la sobrecarga de aprender a escribir la extensión.
- Use Cython para exponer las partes relevantes de la biblioteca C a Python.
- Haga todo en Python, utilizando
ctypes
para comunicarse 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 realmente esa ventaja.
¿Hay más ventajas / desventajas con cualquiera de las dos opciones? ¿Qué enfoque me recomiendan?
Editar: Gracias por todas sus respuestas, proporcionan un buen recurso para cualquiera que quiera hacer algo similar. La decisión, por supuesto, aún debe tomarse para el caso único: no hay una respuesta de "Esto es lo correcto". Para mi propio caso, probablemente usaré ctypes, pero también estoy ansioso por probar Cython en algún otro proyecto.
Al no existir una única respuesta verdadera, aceptar una es algo arbitrario; Elegí la respuesta de FogleBird, ya que proporciona una buena idea de los tipos y actualmente es la respuesta más votada. Sin embargo, sugiero leer todas las respuestas para obtener una buena visión general.
Gracias de nuevo.
Respuestas:
ctypes
es su mejor apuesta para hacerlo rápidamente, ¡y es un placer trabajar con él, ya que todavía 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 de trabajo. (Solo implementé las funciones que necesitábamos, unas 15 funciones).
Anteriormente estábamos usando un módulo de terceros, PyUSB , con el mismo propósito. PyUSB es un módulo de extensión C / Python real. Pero PyUSB no estaba lanzando el GIL al bloquear lecturas / escrituras, lo que nos estaba causando problemas. Entonces escribí nuestro propio módulo usando ctypes, que libera el GIL cuando llama a las funciones nativas.
Una cosa a tener en cuenta es que los ctypes no sabrán acerca de las
#define
constantes y las cosas en la biblioteca que está utilizando, solo las funciones, por lo que tendrá que redefinir esas constantes en su propio código.Aquí hay un ejemplo de cómo se veía el código (muchos recortados, solo tratando de mostrarte la esencia del mismo):
Alguien hizo algunos puntos de referencia sobre las diversas opciones.
Podría dudar más si tuviera que ajustar una biblioteca C ++ con muchas clases / plantillas / etc. Pero ctypes funciona bien con estructuras e incluso puede devolver la llamada a Python.
fuente
ctypes
(forpyinotify
), pero me gustaría entender el problema más a fondo.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.
Entonces, tengo que definir constantes que están allí enwinioctl.h
...ctypes
es mucho más lento que la extensión c ya que el cuello de botella es la interfaz de Python a CAdvertencia: la opinión de un desarrollador principal de Cython 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, seguramente encontrará que tiene que llamar mucho a su biblioteca 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 donde notarás que no puedes 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 o bajarlo también a C. Nuevamente, no puedes hacer eso con ctypes.
Con Cython, OTOH, eres completamente libre de hacer que el código de envoltura y llamada sea tan delgado o grueso como quieras. Puede comenzar con llamadas simples en su código C a partir del código Python normal, y Cython las traducirá en llamadas C nativas, sin ninguna sobrecarga adicional de llamadas 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 realiza 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áselo directamente al registro de devolución de llamada de C. Una vez más, no hay gastos generales, y le brinda 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 realmente críticas en C (o C ++ o Fortran) y llamarlo 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 para hacer cosas simples y hacer que algo funcione rápidamente. Sin embargo, tan pronto como las cosas comiencen a crecer, lo más probable es que llegues al punto en que notes que es mejor que uses Cython desde el principio.
fuente
__radd__
). Esto es especialmente molesto cuando planifica que su clase interactúe con tipos incorporados (por ejemplo,int
yfloat
). Además, los métodos mágicos en cython son un poco defectuosos en general.Cython es una herramienta bastante buena en sí misma, vale la pena aprender, y sorprendentemente se acerca a la sintaxis de Python. Si hace alguna computación científica con Numpy, entonces Cython es el camino a seguir porque se integra con Numpy para operaciones de matriz rápidas.
Cython es un superconjunto del lenguaje Python. Puede arrojar 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 da como resultado una aceleración del 50% porque su código ya no se interpreta.
Para obtener algunas optimizaciones, debe comenzar a contarle a Cython datos adicionales sobre su código, como las declaraciones de tipo. Si le dice lo suficiente, puede reducir el código a puro C. Es decir, un bucle for en Python se convierte en un bucle for en C. Aquí verá ganancias de velocidad masivas. También puede vincular a programas externos de C aquí.
Usar el código de Cython también es increíblemente fácil. Pensé que el manual hace que suene difícil. Literalmente solo haces:
y luego puedes
import mymodule
en tu código de Python y olvidar por completo que se compila en C.En cualquier caso, debido a que Cython es tan fácil de configurar y comenzar a usar, sugiero probarlo para ver si se adapta a sus necesidades. No será un desperdicio si resulta que no es la herramienta que estás buscando.
fuente
mymod.pyx
módulo y luego hágaloimport pyximport; pyximport.install(); import mymod
y la compilación ocurre detrás de escena.runcython mymodule.pyx
. Y a diferencia de pyximport, puede usarlo para tareas de vinculación más exigentes. La única advertencia es que soy yo quien escribió las 20 líneas de bash y podría estar sesgado.Para llamar a una biblioteca de C desde una aplicación de Python, también existe cffi, que es una nueva alternativa para ctypes . Trae una nueva apariencia para FFI:
fuente
Voy a tirar otro por ahí: SWIG
Es fácil de aprender, hace muchas cosas bien y admite muchos más idiomas, por lo que el tiempo dedicado a aprenderlo puede ser bastante útil.
Si usa SWIG, está creando un nuevo módulo de extensión de Python, pero SWIG está haciendo la mayor parte del trabajo pesado por usted.
fuente
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 llevó alrededor de una hora descubrir cómo escribir una, no mucho tiempo.
fuente
ctypes es genial cuando ya tienes un blob de bibliotecas compilado con el que lidiar (como las bibliotecas del sistema operativo). Sin embargo, la sobrecarga de llamadas es severa, por lo que si va a hacer muchas llamadas a la biblioteca y va a escribir el código C de todos modos (o al menos compilarlo), diría que vaya a Cython . No es mucho más trabajo, y será mucho más rápido y más pitónico usar el archivo pyd resultante.
Personalmente, tiendo a usar cython para acelerar rápidamente el código de python (las comparaciones de bucles y enteros son dos áreas en las que cython brilla particularmente), y cuando haya algún código / envoltura más involucrado de otras bibliotecas involucradas, recurriré a Boost.Python . Boost.Python puede ser complicado de configurar, pero una vez que lo tienes funcionando, hace que envolver el código C / C ++ sea sencillo.
Cython también es excelente para envolver numpy (que aprendí de los procedimientos de SciPy 2009 ), pero no he usado numpy, por lo que no puedo comentar sobre eso.
fuente
Si ya tiene una biblioteca con una API definida, creo que
ctypes
es la mejor opción, ya que solo tiene que hacer una pequeña inicialización y luego llamar más o menos a la biblioteca como 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 necesita un nuevo código, por ejemplo, llamar a esa biblioteca y realizar algunas tareas complejas y que requieren mucho tiempo, y luego pasar el resultado a Python.
Otro enfoque, para programas simples, es hacer directamente un proceso diferente (compilado externamente), enviando el resultado a la salida estándar y llamarlo con un módulo de subproceso. A veces es el enfoque más fácil.
Por ejemplo, si crea un programa de consola C que funciona más o menos de esa manera
Podrías llamarlo desde Python
Con un pequeño formateo de cadenas, 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.
fuente
shell=True
podría resultar fácilmente en algún tipo de explotación cuando un usuario realmente obtiene un shell. Está bien cuando el desarrollador es el único usuario, pero en el mundo hay un montón de pinchazos molestos esperando algo como esto.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 usar el compilador de intel en Windows, es mucho más complicado hacer que Cython funcione: debe "explicar" el compilador a cython, recompilar algo con este compilador exacto, etc. Lo que limita significativamente la portabilidad.
fuente
Si está apuntando a Windows y elige envolver algunas bibliotecas C ++ propietarias, pronto descubrirá que las diferentes versiones de
msvcrt***.dll
(Visual C ++ Runtime) son ligeramente incompatibles.Esto significa que es posible que no pueda usarlo
Cython
ya que el resultadowrapper.pyd
está vinculado amsvcr90.dll
(Python 2.7) omsvcr100.dll
(Python 3.x) . Si la biblioteca que está ajustando está vinculada a una versión diferente del tiempo de ejecución, entonces no tiene suerte.Luego, para que las cosas funcionen, deberá crear envoltorios de C para bibliotecas de C ++, vincular ese dll de envoltorio con la misma versión
msvcrt***.dll
que su biblioteca de C ++. Y luego úseloctypes
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:
"Hermosas bibliotecas nativas (en Python) ": http://lucumr.pocoo.org/2013/8/18/beautiful-native-libraries/
fuente
También hay una posibilidad de usar GObject Introspection para bibliotecas que usan GLib .
fuente
Sé que esta es una vieja pregunta, pero esto aparece en Google cuando buscas cosas como
ctypes vs cython
, y la mayoría de las respuestas aquí están escritas por aquellos que ya son competentescython
oc
que pueden no reflejar el tiempo real que necesitas invertir para aprender esos para implementar su solución Soy un principiante completo en ambos. Nunca he tocadocython
antes, y tengo muy poca experienciac/c++
.Durante los últimos dos días, estaba buscando una manera de delegar una parte importante de mi código en algo más bajo que Python. Implementé mi código tanto en
ctypes
comoCython
, que consistía básicamente en dos funciones simples.Tenía una enorme lista de cadenas que necesitaba ser procesada. Aviso
list
ystring
. Ambos tipos no se corresponden perfectamente con los tiposc
, porque las cadenas de Python son por defecto unicode y lasc
cadenas no lo son. Las listas en python simplemente NO son matrices de c.Aquí está mi veredicto. Uso
cython
. Se integra con mayor fluidez a Python y es más fácil trabajar con él en general. Cuando algo sale malctypes
solo te arroja por defecto, al menoscython
te dará advertencias de compilación con un seguimiento de pila siempre que sea posible, y puedes devolver un objeto python válido fácilmentecython
.Aquí hay una cuenta detallada sobre cuánto tiempo necesité invertir en ambos para implementar la misma función. Por cierto, hice muy poca programación en C / C ++:
Tipos:
c
código funciona.c
código.c
código a la base de código real y vi quectypes
no funciona bien con elmultiprocessing
módulo, ya que su controlador no es seleccionable por defecto.multiprocessing
módulo y volví a intentarlo.c
código generó segfaults en mi base de código aunque pasó mi código de prueba. Bueno, probablemente sea mi culpa por no comprobar bien los casos extremos, estaba buscando una solución rápida.c
código y en la segunda o tercera iteración del bucle de Python que lo usa, tuve laUnicodeError
posibilidad de no decodificar un byte en alguna posición, aunque codifiqué y decodifiqué todo explícitamenteEn este punto, decidí buscar una alternativa y decidí investigar
cython
:setuptools
lugar dedistutils
.setup.py
para usar el módulo compilado en mi base de código.multiprocessing
versión de codebase. Funciona.Para el registro, por supuesto, no medí los tiempos exactos de mi inversión. Puede muy bien ser el caso de que mi percepción del tiempo fuera un poco atenta debido al esfuerzo mental requerido mientras lidiaba con los tipos. Pero debe transmitir la sensación de tratar
cython
yctypes
fuente