C / C ++ de Python (CFFI, pybind11)

principal

Continuamos con el tema de cómo llamar a C / C ++ desde Python3 . Ahora usamos las bibliotecas cffi , pybind11 . El método a través de ctypes se discutió en un artículo anterior.


C


Una biblioteca de prueba para demostrar el trabajo con variables globales, estructuras y funciones con argumentos de varios tipos.
prueba.h


typedef struct test_st_s test_st_t; extern int a; extern double b; extern char c; int func_ret_int(int val); double func_ret_double(double val); char *func_ret_str(char *val); char func_many_args(int val1, double val2, char val3, short val4); test_st_t *func_ret_struct(test_st_t *test_st); struct test_st_s { int val1; double val2; char val3; }; 

prueba.c


 #include <stdio.h> #include <stdlib.h> #include "test.h" int a = 5; double b = 5.12345; char c = 'X'; int func_ret_int(int val) { printf("C get func_ret_int: %d\n", val); return val; } double func_ret_double(double val) { printf("C get func_ret_double: %f\n", val); return val; } char * func_ret_str(char *val) { printf("C get func_ret_str: %s\n", val); return val; } char func_many_args(int val1, double val2, char val3, short val4) { printf("C get func_many_args: int - %d, double - %f, char - %c, short - %d\n", val1, val2, val3, val4); return val3; } test_st_t * func_ret_struct(test_st_t *test_st) { if (test_st) { printf("C get test_st: val1 - %d, val2 - %f, val3 - %c\n", test_st->val1, test_st->val2, test_st->val3); } return test_st; } 

La biblioteca es exactamente la misma que en el artículo de ctypes .


CFFI


Esta es una biblioteca para trabajar exclusivamente con C. De la descripción de esta biblioteca:


Interactúa con casi cualquier código C de Python

Algo de esto casi se encontró.


Para el experimento, se utilizó la versión 1.12.3 , puede leer sobre esto aquí .


Un poco sobre esta biblioteca en 2 palabras, CFFI genera su enlace en la parte superior de nuestra biblioteca y la compila en una biblioteca con la que trabajaremos.


Instalación


pip3 instalar cffi


Asamblea


El script de compilación que recopilará el enlace alrededor de nuestra biblioteca.


build.py


 import os import cffi if __name__ == "__main__": ffi = cffi.FFI() #    PATH = os.getcwd() # test.h     #      build.py with open(os.path.join(PATH, "src/c/test.h")) as f: ffi.cdef(f.read()) ffi.set_source("_test", #    cffi,   _ #  test.h,     _test '#include "../src/c/test.h"', #   libtest.so (  ) #  _test.cpython-36m-x86_64-linux-gnu.so ( CFFI) libraries=[os.path.join(PATH, "lib/test"), "./test"], library_dirs=[PATH, 'objs/'], ) #  _test   lib ffi.compile(tmpdir='./lib') 

Pitón


Un ejemplo de trabajo con C desde Python a través de CFFI :


 from cffi import FFI import sys import time #    _test sys.path.append('.') sys.path.append('lib/') sys.path.append('../../lib/') #   import _test ### ## C ### print("CFFI\n") print("C\n") start_time = time.time() ## #    ## print('  :') print('ret func_ret_int: ', _test.lib.func_ret_int(101)) print('ret func_ret_double: ', _test.lib.func_ret_double(12.123456789)) #     cdata   ,     . print('ret func_ret_str: ', _test.ffi.string(_test.lib.func_ret_str('Hello!'.encode('utf-8'))).decode("utf-8")) print('ret func_many_args: ', _test.lib.func_many_args(15, 18.1617, 'X'.encode('utf-8'), 32000).decode("utf-8")) ## #    ## print('\n  :') print('ret a: ', _test.lib.a) #   . _test.lib.a = 22 print('new a: ', _test.lib.a) print('ret b: ', _test.lib.b) print('ret c: ', _test.lib.c.decode("utf-8")) ## #    ## print('\n  :') #      test_st = _test.ffi.new("test_st_t *") test_st.val1 = 5 test_st.val2 = 5.1234567 test_st.val3 = 'Z'.encode('utf-8') ret = _test.lib.func_ret_struct(test_st) #    C print('ret val1 = {}\nret val2 = {}\nret val3 = {}'.format(ret.val1, ret.val2, ret.val3.decode("utf-8"))) #   print("--- %s seconds ---" % (time.time() - start_time)) 

Para trabajar con código C ++ , debe escribir un enlace en C para él. El artículo sobre el método a través de ctypes describe cómo hacer esto. Enlace a continuación.


Pros y contras de CFFI


Pros :


  • sintaxis simple cuando se usa en Python
  • no es necesario volver a compilar la biblioteca fuente

Contras :


  • ensamblaje no conveniente, necesita registrar las rutas a todos los archivos de encabezado y bibliotecas
  • Se crea 1 biblioteca dinámica más, que utiliza el original
  • no es compatible con las siguientes directivas:
     #ifdef __cplusplus extern "C" { #endif ... #ifdef __cplusplus } #endif 

     #ifndef _TEST_H_ #define _TEST_H_ ... #endif /* _TEST_H_ */ 

pybind11


pybind11, por el contrario, está diseñado específicamente para trabajar con C ++ . La versión 2.3.0 se utilizó para el experimento, puede leer sobre esto aquí . Ella no recopila fuentes de C, así que las traduje a fuentes de C ++.


Instalación


pip3 instalar pybind11


Asamblea


Necesitamos escribir un script de compilación para nuestra biblioteca.
build.py


 import pybind11 from distutils.core import setup, Extension ext_modules = [ Extension( '_test', #    pybind11 ['src/c/test.cpp'], #     include_dirs=[pybind11.get_include()], #     pybind11 language='c++', #   extra_compile_args=['-std=c++11'], #  ++11 ), ] setup( name='_test', #    pybind11 version='1.0.0', author='djvu', author_email='djvu@inbox.ru', description='pybind11 extension', ext_modules=ext_modules, requires=['pybind11'], #    pybind11 package_dir = {'': 'lib'} ) 

Lo ejecutamos:


 python3 setup.py build --build-lib=./lib 

C ++


En la fuente de la biblioteca necesita agregar:


  • archivo de encabezado pybind11
     #include <pybind11/pybind11.h> 
  • macro que nos permite definir un módulo python
     PYBIND11_MODULE(_test, m) 
  • Ejemplo de macro para una biblioteca de prueba:

 namespace py = pybind11; // _test    PYBIND11_MODULE(_test, m) { /* *   */ m.def("func_ret_int", &func_ret_int); m.def("func_ret_double", &func_ret_double); m.def("func_ret_str", &func_ret_str); m.def("func_many_args", &func_many_args); m.def("func_ret_struct", &func_ret_struct); /* *    */ m.attr("a") = a; m.attr("b") = b; m.attr("c") = c; /* *  */ py::class_<test_st_t>(m, "test_st_t") .def(py::init()) //  .    ,    Python //       C,  C++  (   C     ++   ) .def_readwrite("val1", &test_st_t::val1) //   .def_readwrite("val2", &test_st_t::val2) .def_readwrite("val3", &test_st_t::val3); }; 

Pitón


Un ejemplo de trabajo con C desde Python a través de pybind11 :


 import sys import time #    _test sys.path.append('lib/') #   import _test ### ## C ### print("pybind11\n") print("C\n") start_time = time.time() ## #    ## print('  :') print('ret func_ret_int: ', _test.func_ret_int(101)) print('ret func_ret_double: ', _test.func_ret_double(12.123456789)) #     cdata   . print('ret func_ret_str: ', _test.func_ret_str('Hello!'.encode('utf-8'))) print('ret func_many_args: ', _test.func_many_args(15, 18.1617, 'X'.encode('utf-8'), 32000)) ## #    ## print('\n  :') print('ret a: ', _test.a) #   . _test.a = 22 print('new a: ', _test.a) print('ret b: ', _test.b) print('ret c: ', _test.c) ## #    ## print('\n  :') #      _test_st = _test.test_st_t() #print(dir(_test_st)) _test_st.val1 = 5 _test_st.val2 = 5.1234567 _test_st.val3 = 'Z'.encode('utf-8') ret = _test.func_ret_struct(_test_st) #    C print('ret val1 = {}\nret val2 = {}\nret val3 = {}'.format(ret.val1, ret.val2, ret.val3)) #   print("--- %s seconds ---" % (time.time() - start_time)) 

Pros y contras de pybind11


Pros :


  • sintaxis simple cuando se usa en Python

Contras :


  • necesita editar fuentes C ++ o escribir un enlace para ellas
  • es necesario recolectar la biblioteca necesaria de la fuente

El tiempo promedio de ejecución de la prueba en cada método con 1000 comienza:


  • tipos: - 0.0004987692832946777 segundos ---
  • CFFI: - 0.00038521790504455566 segundos ---
  • pybind: - 0.0004547207355499268 segundos ---

+, - porque los resultados fueron ligeramente diferentes cada vez. Además, se dedicó tiempo a imprimir, que era demasiado flojo para apagarlo (tome este tiempo como una constante, porque será ~ igual en todas las pruebas). Pero aún así, hay una diferencia horaria en las llamadas a funciones y en obtener los resultados de ellas.


Referencias


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


All Articles