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

utama

Kami melanjutkan topik tentang cara memanggil C / C ++ dari Python3 . Sekarang kita menggunakan pustaka cffi , pybind11 . Metode melalui ctypes dibahas dalam artikel sebelumnya.


C


Pustaka uji untuk mendemonstrasikan bekerja dengan variabel global, struktur, dan fungsi dengan argumen dari berbagai jenis.
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; } 

Perpustakaan persis sama dengan di artikel ctypes .


CFFI


Ini adalah perpustakaan untuk bekerja secara eksklusif dengan C. Dari deskripsi perpustakaan ini:


Berinteraksi dengan hampir semua kode C dari Python

Sebagian dari ini hampir ditemukan.


Untuk percobaan, versi 1.12.3 digunakan , Anda dapat membacanya di sini .


Sedikit tentang perpustakaan ini dalam 2 kata, CFFI menghasilkan pengikatannya di atas perpustakaan kami dan mengkompilasinya menjadi perpustakaan yang dengannya kami akan bekerja.


Instalasi


pip3 instal cffi


Majelis


Skrip pembuatan yang akan mengumpulkan ikatan di sekitar perpustakaan kami.


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


Contoh bekerja dengan C dari Python melalui 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)) 

Untuk bekerja dengan kode C ++ , Anda harus menulis ikatan C untuknya. Artikel tentang metode melalui ctypes menjelaskan bagaimana melakukan ini. Tautan di bawah ini.


Pro dan Kontra CFFI


Pro :


  • sintaksis sederhana ketika digunakan dalam Python
  • tidak perlu mengkompilasi ulang pustaka sumber

Cons :


  • perakitan tidak nyaman, Anda harus mendaftarkan jalur ke semua file header dan perpustakaan
  • 1 perpustakaan yang lebih dinamis dibuat, yang menggunakan yang asli
  • tidak mendukung arahan berikut:
     #ifdef __cplusplus extern "C" { #endif ... #ifdef __cplusplus } #endif 

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

pybind11


pybind11, sebaliknya, dirancang khusus untuk bekerja dengan C ++ . Versi 2.3.0 digunakan untuk percobaan, Anda dapat membacanya di sini . Dia tidak mengumpulkan sumber C, jadi saya menerjemahkannya ke sumber C ++.


Instalasi


pip3 instal pybind11


Majelis


Kita perlu menulis skrip build untuk pustaka kita.
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'} ) 

Kami melaksanakannya:


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

C ++


Di sumber perpustakaan Anda perlu menambahkan:


  • file header pybind11
     #include <pybind11/pybind11.h> 
  • makro yang memungkinkan kita untuk mendefinisikan modul python
     PYBIND11_MODULE(_test, m) 
  • contoh makro untuk perpustakaan tes:

 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


Contoh bekerja dengan C dari 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)) 

Pro dan kontra dari pybind11


Pro :


  • sintaksis sederhana ketika digunakan dalam Python

Cons :


  • Anda perlu mengedit sumber C ++ , atau menulis ikatan untuknya
  • perlu untuk mengumpulkan perpustakaan yang diperlukan dari sumber

Waktu pelaksanaan pengujian rata-rata pada setiap metode dengan 1000 dimulai:


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

+, - karena hasilnya sedikit berbeda setiap kali. Plus, waktu dihabiskan untuk mencetak, yang terlalu malas untuk saya matikan (anggap waktu ini konstan, karena akan ~ sama di semua pengujian). Tapi tetap saja, ada perbedaan waktu dalam fungsi panggilan dan mendapatkan hasil dari mereka.


Referensi


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


All Articles