Cara membuat pembungkus Python dan tidak menjadi gila

Baru-baru ini saya membaca sebuah artikel tentang Habré tentang alat yang sangat berguna, dan karena saya sudah lama mencari proyek untuk mulai berkontribusi, saya memutuskan untuk melihat apa yang ada di github dan bagaimana saya dapat membantu. Satu masalah adalah tentang membuat pembungkus (saya akan menggunakan pembungkus nanti) untuk C-library. Pada saat itu saya berpikir, "Oh, sesuatu yang menarik, saya yakin itu akan memakan waktu tidak lebih dari satu jam." Betapa saya salah.


Dalam artikel ini, saya memutuskan untuk menunjukkan bukan satu cara untuk menyelesaikan masalah yang sama, tetapi beberapa opsi berbeda. Saya akan menunjukkan kepada Anda opsi untuk membuat modul dalam Python dengan kompilasi dalam C, menggunakan pustaka tulisan-sendiri C kecil di Python, dan - opsi terakhir - menggunakan pustaka C besar di Python tanpa file-file pxd yang sakit.


Cython


Buku telah ditulis tentang ini, ada banyak artikel, termasuk di Habré, jadi saya tidak akan terlalu fokus pada instalasi atau beberapa hal mendasar. Baca lebih lanjut di sini


Dengan menggunakan Cython, kita dapat memecahkan beberapa masalah. Untuk beberapa contoh kode C dalam python, biasanya cocok dan sebagian menyelesaikan masalah dengan impor perpustakaan.


Mari kita lihat contoh sederhana dari dokumentasi resmi.


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

Simpan file ini sebagai fib.pyx .
.pyx adalah format khusus file Cython, yang mirip dengan .c untuk kode C dan berisi beberapa fungsi. Ada juga .pxd , di C adalah .h dan berisi deskripsi fungsi, struktur, dll.


Untuk berinteraksi dengan fungsi fib, kita perlu "mengkompilasi" kode tersebut. Untuk melakukan ini, buat setup.py dengan konten ini.


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

Setelah menjalankan perintah python3 setup.py build_ext --inplace Anda dapat mengimpornya dengan python biasa dan menikmati kecepatan kerja seperti pada normal bahasa yang dikompilasi.


 import fib fib.fib(2000) 

Tapi di sini kita menulis kode Python dan mengubahnya menjadi C, tetapi bagaimana dengan menulis kode C dan menjalankannya dengan Python?


Tidak masalah Kami membuat folder baru, di dalam kami membuat folder lib di mana kami akan membuat lib/include dan lib/src , pada kenyataannya, semua orang yang bekerja dengan C sudah tahu apa yang akan ada di sana. Di folder utama, buat folder python_wrap lain.


Mari kita struct.h ke lib/include dan buat struct.h , di mana kita akan menjelaskan satu fungsi dan melihat bagaimana bekerja dengan struktur dalam C melalui Cython.


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

Mari kita buat file lain, yang akan kita panggil include.h , ia akan memiliki fungsi lain dan mengimpor struktur dari struct.h


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

Sekarang kita akan menjelaskan fungsi-fungsi ini di file 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; } 

Ya, saya tidak berpura-pura menjadi orisinal untuk nama-nama variabel, tetapi kami hampir menyelesaikan bagian-C. Apa lagi Tambahkan Makefile, atau lebih tepatnya CMake. Di folder lib , buat 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}) 

Dari direktori utama, kita perlu mengindikasikan bahwa kita memiliki proyek untuk dikompilasi di folder lib . Buat file CMakeLists.txt lain, tetapi sudah di root.


 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) 

Di sini saya menggunakan file kecil yang mentransfer struktur pembungkus Python ke direktori build sehingga Anda dapat mengkompilasi file Python. Ini mungkin tidak diperlukan jika Anda melewati jalur relatif ke direktori include dan tempat perpustakaan akan berada. Misalnya, jika pustaka sudah dikompilasi dan diinstal dalam sistem, maka kita akan mengatur path ke direktori sistem, tetapi lebih lanjut tentang itu nanti.


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

Sebelum merakit proyek kami, mari kita lihat bagian Python. Dalam folder python_wrap membuat dua file main.pxd dan main.pyx . Di main.pxd kita perlu menjelaskan apa yang kita miliki di file *.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); 

Menggunakan cdef extern from "include/include.h" , kami menunjukkan file mana yang akan kami uraikan. Selanjutnya adalah ctypedef struct struct_test: deskripsi struktur sehingga dapat digunakan dari kode Python. Pada akhirnya, pada kenyataannya, deskripsi dua fungsi. Saya ingin mencatat bahwa kita perlu menjelaskan semua include yang ada di include.h , jika itu mengimpor struktur dan fungsi dari file header lain, kami percaya bahwa semua ini ada dalam satu file.


Di main.pyx kita menulis fungsi transisi dari Python ke C. Ini tidak perlu, tetapi mengapa memuat kode Python dengan struktur untuk C. Untuk membuat struktur, cukup mendefinisikan kamus dengan semua parameter.


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

Sekarang kita harus menyatukan semuanya. Tambahkan file setup.py ke root proyek, seperti yang kami lakukan sebelumnya.


 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) 

Untuk mengkompilasi kode C dan mengkompilasi perpustakaan kami, kami akan membuat skrip bash sederhana.


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

Kami meluncurkan dan memeriksa


 $ 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


Contoh sebelumnya sangat sederhana dan mudah, tetapi bagaimana jika Anda perlu membungkus perpustakaan yang sangat besar, tulis semua file .pxd dengan tangan Anda untuk waktu yang sangat lama dan sulit, sehingga muncul pertanyaan yang masuk akal, apa yang dapat digunakan untuk mengotomatiskan proses?


Kami git clone https://github.com/davidjamesca/ctypesgen.git repositori git clone https://github.com/davidjamesca/ctypesgen.git . Buka pustaka build/lib/ dibangun sebelumnya build/lib/ dan jalankan skrip.


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

Setelah itu, kami memeriksa pekerjaan.


 $ 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 

Nah, kembali ke pertanyaan pustaka yang sudah diinstal, katakanlah kita ingin membuat pembungkus pada pustaka neon (yang sudah diinstal pada sistem dengan cara yang mudah), seperti yang ditunjukkan pada 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'] 

Akhirnya, tautan ke github , bagaimana mungkin tanpa itu.

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


All Articles