Comment créer un wrapper Python et ne pas devenir fou

Récemment, j'ai lu un article sur Habré sur un outil très utile, et comme je cherchais depuis longtemps une sorte de projet pour commencer à contribuer, j'ai décidé de voir ce qui se trouve sur le github et comment je peux aider. Un problème concernait la création d'un wrapper (j'utiliserai le wrapper plus tard) pour la bibliothèque C. À ce moment, j'ai pensé: "Oh, quelque chose d'intéressant, je suis sûr que cela ne prendra pas plus d'une heure." Combien je me trompais.


Dans cet article, j'ai décidé de montrer non pas une façon de résoudre un problème similaire, mais plusieurs options différentes. Je vais vous montrer les options pour créer des modules en Python avec compilation en C, en utilisant une petite bibliothèque auto-écrite C en Python, et - la dernière option - en utilisant une grande bibliothèque C en Python sans fichiers pain et pxd.


Cython


Des livres ont déjà été écrits à ce sujet, il y a beaucoup d'articles, y compris sur Habré, donc je ne vais pas trop me concentrer sur l'installation ou quelques trucs de base. En savoir plus ici


En utilisant Cython, nous pouvons résoudre plusieurs problèmes. Pour certaines instances de code C en python, il s'adapte généralement parfaitement et résout partiellement le problème avec les importations de bibliothèque.


Regardons un exemple simple de la documentation officielle.


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

Enregistrez ce fichier sous fib.pyx .
.pyx est un format spécial de fichiers Cython, qui est similaire au .c pour le code C et contient certaines fonctionnalités. Il y a aussi .pxd , en C c'est .h et contient une description des fonctions, structures, etc.


Afin d'interagir d'une manière ou d'une autre avec la fonction fib, nous devons "compiler" le code. Pour ce faire, créez setup.py avec ce contenu.


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

Après avoir exécuté la commande python3 setup.py build_ext --inplace vous pouvez l'importer en python normal et profiter de la vitesse de travail comme dans normal langues compilées.


 import fib fib.fib(2000) 

Mais ici, nous avons écrit du code Python et l'avons transformé en C, mais qu'en est-il de l'écriture du code C et de son exécution en Python?


Pas de problème. Nous créons un nouveau dossier, à l'intérieur nous créons le dossier lib dans lequel nous allons créer lib/include et lib/src , en fait, tous ceux qui ont travaillé avec C savent déjà ce qu'il y aura. Dans le dossier principal, créez un autre dossier python_wrap .


Allons dans lib/include et créons struct.h , dans lequel nous allons décrire une fonction et voir comment travailler avec des structures en C via Cython.


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

Créons un autre fichier, que nous appellerons include.h , il aura une autre fonction et importera la structure de struct.h


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

Nous allons maintenant décrire ces fonctions dans le fichier 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; } 

Oui, je ne prétends pas être original pour les noms de variables, mais nous avons presque terminé la partie C. Quoi d'autre? Ajoutez un Makefile, ou plutôt CMake. Dans le dossier lib , créez 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}) 

Depuis le répertoire principal, nous devons indiquer que nous avons un projet à compiler dans le dossier lib . Créez un autre fichier CMakeLists.txt , mais déjà à la racine.


 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) 

Ici, j'utilise un petit fichier qui transfère la structure du wrapper Python dans le répertoire de construction afin que vous puissiez compiler les fichiers Python. Cela peut ne pas être nécessaire si vous transmettez des chemins relatifs au répertoire d' include et à l'emplacement de la bibliothèque. Par exemple, si la bibliothèque est déjà compilée et installée dans le système, nous définirons les chemins d'accès aux répertoires système, mais plus à ce sujet plus tard.


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

Avant d'assembler notre projet, jetons un œil à la partie Python. Dans le dossier python_wrap , python_wrap créons deux fichiers main.pxd et main.pyx . Dans main.pxd nous devons décrire ce que nous avons dans les fichiers *.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); 

En utilisant cdef extern from "include/include.h" , nous indiquons quel fichier nous allons décrire. Vient ensuite la ctypedef struct struct_test: description de la structure afin qu'elle puisse être utilisée à partir du code Python. À la fin, en fait, une description de deux fonctions. Je veux noter que nous devons décrire tous les include qui sont dans include.h , s'il importe une structure et une fonction à partir d'un autre fichier d'en-tête, nous pensons que tout cela est dans un fichier.


Dans main.pyx nous écrivons les fonctions de transition de Python vers C. Ce n'est pas nécessaire, mais pourquoi charger du code Python avec des structures pour C. Pour créer une structure, il suffit de définir un dictionnaire avec tous les paramètres.


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

Maintenant, nous devons rassembler tout cela. Ajoutez le fichier setup.py à la racine du projet, comme nous l'avons fait auparavant.


 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) 

Afin de compiler du code C et de compiler notre bibliothèque, nous allons créer un simple script bash.


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

Nous lançons et vérifions


 $ 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


L'exemple précédent était très simple et direct, mais que se passe-t-il si vous avez besoin d'envelopper une très grande bibliothèque, d'écrire tous les fichiers .pxd avec vos mains pendant très longtemps et est difficile, donc il y a une question raisonnable, que peut-on utiliser pour automatiser le processus?


Nous git clone https://github.com/davidjamesca/ctypesgen.git référentiel de git clone https://github.com/davidjamesca/ctypesgen.git . Accédez à la bibliothèque précédemment construite build/lib/ et exécutez le script.


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

Après cela, nous vérifions le travail.


 $ 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 

Eh bien, revenons à la question des bibliothèques déjà installées, disons que nous voulons créer un wrapper sur une bibliothèque néon (qui est déjà installée sur le système de manière pratique), comme indiqué dans le 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'] 

Enfin, un lien vers github , comment pourrait-il être sans lui.

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


All Articles