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

principal

Nous continuons le sujet de la façon d'appeler C / C ++ à partir de Python3 . Nous utilisons maintenant les bibliothèques cffi , pybind11 . La méthode par ctypes a été discutée dans un article précédent.


C


Une bibliothèque de tests pour montrer comment travailler avec des variables globales, des structures et des fonctions avec des arguments de différents types.
test.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; }; 

test.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 bibliothèque est exactement la même que dans l'article ctypes .


CFFI


Ceci est une bibliothèque pour travailler exclusivement avec C. D'après la description de cette bibliothèque:


Interagissez avec presque tous les codes C de Python

Une partie de cela a été presque trouvée.


Pour l'expérience, la version 1.12.3 a été utilisée , vous pouvez en lire plus ici .


Un peu sur cette bibliothèque en 2 mots, CFFI génère sa liaison au dessus de notre bibliothèque et la compile dans une bibliothèque avec laquelle nous travaillerons.


L'installation


pip3 installer cffi


Assemblage


Le script de construction qui collectera la liaison autour de notre bibliothèque.


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') 

Python


Un exemple de travail avec C de Python via 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)) 

Pour travailler avec du code C ++ , vous devez écrire une liaison C pour cela. L'article sur la méthode via ctypes décrit comment procéder. Lien ci-dessous.


Avantages et inconvénients de CFFI


Avantages :


  • syntaxe simple lorsqu'elle est utilisée en Python
  • pas besoin de recompiler la bibliothèque source

Inconvénients :


  • assemblage pas pratique, vous devez enregistrer les chemins d'accès à tous les fichiers d'en-tête et bibliothèques
  • 1 bibliothèque plus dynamique est créée, qui utilise l'original
  • ne prend pas en charge les directives suivantes:
     #ifdef __cplusplus extern "C" { #endif ... #ifdef __cplusplus } #endif 

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

pybind11


pybind11, en revanche, est conçu spécifiquement pour travailler avec C ++ . La version 2.3.0 a été utilisée pour l'expérience, vous pouvez en lire plus ici . Elle ne collecte pas de sources C, donc je les ai traduites en sources C ++.


L'installation


pip3 installer pybind11


Assemblage


Nous devons écrire un script de construction pour notre bibliothèque.
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'} ) 

Nous l'exécutons:


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

C ++


Dans la source de la bibliothèque, vous devez ajouter:


  • fichier d'en-tête pybind11
     #include <pybind11/pybind11.h> 
  • macro qui nous permet de définir un module python
     PYBIND11_MODULE(_test, m) 
  • exemple de macro pour une bibliothèque de test:

 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); }; 

Python


Un exemple de travail avec C depuis Python via 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)) 

Avantages et inconvénients de pybind11


Avantages :


  • syntaxe simple lorsqu'elle est utilisée en Python

Inconvénients :


  • vous devez modifier les sources C ++ ou écrire une liaison pour elles
  • il est nécessaire de collecter la bibliothèque nécessaire à partir de la source

Le temps d'exécution moyen des tests sur chaque méthode avec 1000 démarrages:


  • ctypes: - 0,0004987692832946777 secondes ---
  • CFFI: - 0,00038521790504455566 secondes ---
  • pybind: - 0,0004547207355499268 secondes ---

+, - parce que les résultats étaient légèrement différents à chaque fois. De plus, j'ai passé du temps à imprimer, ce que j'étais trop paresseux pour désactiver (prenez ce temps comme une constante, car ce sera ~ le même dans tous les tests). Mais malgré tout, il y a une différence de temps entre les appels de fonction et l'obtention des résultats.


Les références


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


All Articles