Cómo crear Python wrapper y no volverse loco

Recientemente leí un artículo sobre Habré sobre una herramienta muy útil, y como había estado buscando algún tipo de proyecto durante mucho tiempo para comenzar a contribuir, decidí ver qué hay en el github y cómo puedo ayudar. Un problema era crear un contenedor (usaré el contenedor más adelante) para la biblioteca C. En ese momento pensé: "Oh, algo interesante, estoy seguro de que no tomará más de una hora". Cuánto me equivoqué.


En este artículo, decidí mostrar no una forma de resolver un problema similar, sino varias opciones diferentes. Le mostraré opciones para crear módulos en Python con compilación en C, usando una pequeña biblioteca autoescrita C en Python y, la última opción, usando una gran biblioteca C en Python sin dolor y archivos pxd.


Cython


Ya se han escrito libros sobre esto, hay muchos artículos, incluso sobre Habré, por lo que no me enfocaré demasiado en la instalación o en algunas cosas básicas. Lee más aquí


Usando Cython, podemos resolver varios problemas. Para algunos casos de código C en python, generalmente se adapta perfectamente y resuelve parcialmente el problema con las importaciones de la biblioteca.


Veamos un ejemplo simple de la documentación oficial.


from __future__ import print_function def fib(n): """Print the Fibonacci series up to n.""" a, b = 0, 1 while b < n: print(b, end=' ') a, b = b, a + b print() 

Guarde este archivo como fib.pyx .
.pyx es un formato especial de archivos de Cython, que es similar a .c para el código C y contiene algunas funcionalidades. También hay .pxd , en C es .h y contiene una descripción de funciones, estructuras, etc.


Para interactuar de alguna manera con la función fib, necesitamos "compilar" el código. Para hacer esto, cree setup.py con este contenido.


 from distutils.core import setup from Cython.Build import cythonize setup( ext_modules=cythonize("fib.pyx"), ) 

Después de ejecutar el python3 setup.py build_ext --inplace puede importarlo en python normal y disfrutar de la velocidad del trabajo como en normal lenguajes compilados


 import fib fib.fib(2000) 

Pero aquí escribimos el código Python y lo convertimos en C, pero ¿qué hay de escribir el código C y ejecutarlo en Python?


No hay problema Creamos una nueva carpeta, dentro creamos la carpeta lib en la que crearemos lib/include y lib/src , de hecho, todos los que trabajaron con C ya saben lo que habrá allí. En la carpeta principal, cree otra carpeta python_wrap .


Vayamos a lib/include y cree struct.h , en el que describiremos una función y veremos cómo trabajar con estructuras en C a través de Cython.


 typedef struct struct_test{ int a; int b; } struct_test; int minus(struct_test a); 

Creemos otro archivo, que llamaremos include.h , tendrá otra función e importará la estructura desde struct.h


 #include "struct.h" int sum(struct_test param_in_struct); 

Ahora describiremos estas funciones en el archivo lib/src/test_main.c


 #include "include.h" int sum(struct_test param_in_struct){ return param_in_struct.a+param_in_struct.b; } int minus(struct_test param_in_struct){ return param_in_struct.a-param_in_struct.b; } 

Sí, no pretendo ser original para nombres de variables, pero casi hemos terminado la parte C. Que mas Agregue un Makefile, o más bien CMake. En la carpeta lib , cree CMakeLists.txt .


 set (TARGET "mal") include_directories( include src ) set (SOURCES ./src/test_main.c ) set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") add_library(${TARGET} SHARED ${SOURCES}) target_link_libraries(${TARGET} ${LINKLIBS}) add_library(${TARGET}static STATIC ${SOURCES}) target_link_libraries(${TARGET}static ${LINKLIBS}) 

Desde el directorio principal, debemos indicar que tenemos un proyecto para compilar en la carpeta lib . Cree otro archivo CMakeLists.txt , pero ya en la raíz.


 cmake_minimum_required(VERSION 2.8.2 FATAL_ERROR) cmake_policy(VERSION 2.8) project( TEST ) set (CMAKE_C_FLAGS "-Werror -Wall -Wextra -Wno-unused-parameter -D_GNU_SOURCE -std=c11 -O3 -g ${CMAKE_C_FLAGS}") add_custom_target( ReplicatePythonSourceTree ALL ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/ReplicatePythonSourceTree.cmake ${CMAKE_CURRENT_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) include( GNUInstallDirs ) add_subdirectory(lib) 

Aquí uso un pequeño archivo que transfiere la estructura de envoltura de Python al directorio de compilación para que pueda compilar los archivos de Python. Esto puede no ser necesario si pasa rutas relativas al directorio de include y al lugar donde estará la biblioteca. Por ejemplo, si la biblioteca ya está compilada e instalada en el sistema, entonces estableceremos las rutas a los directorios del sistema, pero más sobre eso más adelante.


cmake / ReplicatePythonSourceTree.cmake
 # Note: when executed in the build dir, then CMAKE_CURRENT_SOURCE_DIR is the # build dir. file( COPY setup.py DESTINATION "${CMAKE_ARGV3}" FILES_MATCHING PATTERN "*.py" ) file( COPY lib/src lib/include DESTINATION "${CMAKE_ARGV3}") file(GLOB MY_WRAP "python_wrap/*" ) file( COPY ${MY_WRAP} DESTINATION "${CMAKE_ARGV3}") 

Antes de armar nuestro proyecto, echemos un vistazo a la parte de Python. En la carpeta python_wrap creamos dos archivos main.pxd y main.pyx . En main.pxd necesitamos describir lo que tenemos en los archivos *.h .


 cdef extern from "include/include.h": ctypedef struct struct_test: int a int b int sum(struct_test param_in_struct); int minus(struct_test param_in_struct); 

Usando cdef extern from "include/include.h" , indicamos qué archivo describiremos. Luego viene el ctypedef struct struct_test: descripción de la estructura para que pueda usarse desde el código Python. Al final, de hecho, una descripción de dos funciones. Quiero señalar que necesitamos describir todas las inclusiones que están en include.h , si importa una estructura y función de otro archivo de encabezado, creemos que todo esto está en un solo archivo.


En main.pyx escribimos las funciones de transición de Python a C. Esto no es necesario, pero ¿por qué cargar el código de Python con estructuras para C. Para crear una estructura, es suficiente definir un diccionario con todos los parámetros.


 from main cimport sum, minus def sum_py(int x, int y): return sum({"a":x,"b":y}) def minus_py(int x, int y): return minus({"a":x,"b":y}) 

Ahora tenemos que hacer que todo se una. Agregue el archivo setup.py a la raíz del proyecto, como lo hicimos antes.


 from distutils.core import setup from distutils.extension import Extension from Cython.Distutils import build_ext ext_modules = [Extension('main', ['main.pyx'], libraries=['mal'], library_dirs=['lib/'])] setup(name = 'work extension module', cmdclass = {'build_ext': build_ext}, ext_modules = ext_modules) 

Para compilar código C y compilar nuestra biblioteca, crearemos un script bash simple.


 #!/bin/sh rm -rf build; mkdir build && cd build cmake .. && make $@ python3 setup.py build_ext -i 

Lanzamos y verificamos


 $ sh build.sh $ python3 > import build.main as main > dir(main) [.... 'minus_py', 'sum_py'] > main.minus_py(10,2) 8 > main.sum_py(10,2) 12 

Ctypesgen


El ejemplo anterior fue muy simple y directo, pero ¿qué pasa si necesita envolver una biblioteca muy grande, escribir todos los archivos .pxd con las manos durante mucho tiempo y es difícil, por lo que hay una pregunta razonable, qué se puede utilizar para automatizar el proceso?


git clone https://github.com/davidjamesca/ctypesgen.git repositorio de git clone https://github.com/davidjamesca/ctypesgen.git . Vaya a la biblioteca build/lib/ previamente build/lib/ y ejecute el script.


 python3 ~/ctypesgen/run.py -lmal ../include/*.h -o main_wrap.py 

Después de eso, verificamos el trabajo.


 $ python3 > import main_wrap as main > dir(main) [... 'struct_test', 'minus', 'sum'] > main.sum(main.struct_struct_test(1,2)) 3 > main.minus(main.struct_struct_test(1,2)) -1 

Bueno, volviendo a la cuestión de las bibliotecas ya instaladas, digamos que queremos hacer un contenedor en una biblioteca de neón (que ya está instalada en el sistema de alguna manera conveniente), como se muestra en el archivo Readme Stypesgen.


 $ ctypesgen.py -lneon /usr/local/include/neon/ne_*.h -o neon.py $ python > import neon > dir(neon) [...,'sys', 'time_t', 'union_ne_session_status_info_u', 'wstring_at'] 

Finalmente, un enlace a github , cómo podría ser sin él.

Source: https://habr.com/ru/post/467615/


All Articles