Wie man einen Python-Wrapper erstellt und nicht verrückt wird

Kürzlich habe ich einen Artikel über Habré über ein sehr nützliches Tool gelesen, und da ich lange nach einem Projekt gesucht hatte, um einen Beitrag zu leisten, habe ich mich entschlossen zu sehen, was auf dem Github steht und wie ich helfen kann. Ein Problem bestand darin, einen Wrapper (ich werde ihn später verwenden) für die C-Bibliothek zu erstellen. In diesem Moment dachte ich: "Oh, etwas Interessantes, ich bin sicher, es wird nicht länger als eine Stunde dauern." Wie sehr habe ich mich geirrt.


In diesem Artikel habe ich beschlossen, nicht einen Weg zur Lösung eines ähnlichen Problems aufzuzeigen, sondern verschiedene Optionen. Ich werde Ihnen Optionen zum Erstellen von Modulen in Python mit Kompilierung in C unter Verwendung einer kleinen selbstgeschriebenen Bibliothek C in Python und - die letzte Option - unter Verwendung einer großen C-Bibliothek in Python ohne Schmerzen und pxd-Dateien zeigen.


Cython


Es wurden bereits Bücher darüber geschrieben, es gibt viele Artikel, einschließlich über Habré, daher werde ich mich nicht zu sehr auf die Installation oder einige grundlegende Dinge konzentrieren. Lesen Sie hier mehr


Mit Cython können wir verschiedene Probleme lösen. Für einige Fälle von C-Code in Python passt es im Allgemeinen perfekt und löst das Problem mit Bibliotheksimporten teilweise.


Schauen wir uns ein einfaches Beispiel aus der offiziellen Dokumentation an.


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

Speichern Sie diese Datei als fib.pyx .
.pyx ist ein spezielles Format für Cython-Dateien, das .c für C-Code ähnelt und einige Funktionen enthält. Es gibt auch .pxd , in C ist es .h und enthält eine Beschreibung von Funktionen, Strukturen usw.


Um irgendwie mit der Fib-Funktion zu interagieren, müssen wir den Code "kompilieren". Erstellen Sie dazu die setup.py mit diesem Inhalt.


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

Nachdem Sie den python3 setup.py build_ext --inplace Sie ihn in python3 setup.py build_ext --inplace Python importieren und die Arbeitsgeschwindigkeit wie in genießen normal kompilierte Sprachen.


 import fib fib.fib(2000) 

Aber hier haben wir Python-Code geschrieben und in C umgewandelt, aber was ist mit dem Schreiben von C-Code und dem Ausführen in Python?


Kein Problem. Wir erstellen einen neuen Ordner, in dem wir den lib Ordner erstellen, in dem wir lib/include und lib/src erstellen. Tatsächlich weiß jeder, der mit C gearbeitet hat, bereits, was dort sein wird. Erstellen Sie im python_wrap einen weiteren python_wrap Ordner.


Gehen wir zu lib/include und erstellen Sie struct.h , in der wir eine Funktion beschreiben und sehen, wie Sie mit Strukturen in C bis Cython arbeiten.


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

Erstellen wir eine weitere Datei, die wir include.h nennen. Sie hat eine andere Funktion und importiert die Struktur aus struct.h


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

Jetzt werden wir diese Funktionen in der Datei 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; } 

Ja, ich gebe nicht vor, für Variablennamen original zu sein, aber wir haben den C-Teil fast fertiggestellt. Was sonst? Fügen Sie ein Makefile oder besser CMake hinzu. Erstellen CMakeLists.txt im Ordner lib die 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}) 

Im Hauptverzeichnis müssen wir angeben, dass wir ein Projekt im lib Ordner kompilieren müssen. Erstellen Sie eine weitere CMakeLists.txt Datei, jedoch bereits im Stammverzeichnis.


 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) 

Hier verwende ich eine kleine Datei, die die Python-Wrapper-Struktur in das Build-Verzeichnis überträgt, damit Sie die Python-Dateien kompilieren können. Dies ist möglicherweise nicht erforderlich, wenn Sie relative Pfade an das include Verzeichnis und den Ort übergeben, an dem sich die Bibliothek befindet. Wenn die Bibliothek beispielsweise bereits kompiliert und im System installiert ist, legen wir die Pfade zu den Systemverzeichnissen fest, aber dazu später mehr.


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

Bevor wir unser Projekt zusammenstellen, werfen wir einen Blick auf den Python-Teil. Im Ordner python_wrap erstellen python_wrap zwei Dateien main.pxd und main.pyx . In main.pxd wir beschreiben, was wir in *.h Dateien haben.


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

Mit cdef extern from "include/include.h" wir an, welche Datei wir beschreiben werden. Als nächstes kommt die ctypedef struct struct_test: Beschreibung der Struktur, damit sie aus Python-Code verwendet werden kann. Am Ende tatsächlich eine Beschreibung von zwei Funktionen. Ich möchte darauf hinweisen, dass wir alle Include- include.h in include.h müssen. Wenn eine Struktur und Funktion aus einer anderen Header-Datei importiert wird, glauben wir, dass sich all dies in einer Datei befindet.


In main.pyx schreiben wir die Funktionen des Übergangs von Python zu C. Dies ist nicht erforderlich, aber warum Python-Code mit Strukturen für C laden. Um eine Struktur zu erstellen, reicht es aus, ein Wörterbuch mit allen Parametern zu definieren.


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

Jetzt müssen wir alles zusammenbringen. Fügen Sie die Datei setup.py wie zuvor zum Projektstamm hinzu.


 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) 

Um C-Code zu kompilieren und unsere Bibliothek zu kompilieren, erstellen wir ein einfaches Bash-Skript.


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

Wir starten und überprüfen


 $ 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


Das vorherige Beispiel war sehr einfach und unkompliziert, aber was ist, wenn Sie eine sehr große Bibliothek einpacken müssen, alle .pxd- Dateien sehr lange mit Ihren Händen schreiben müssen und es schwierig ist? Es gibt also eine vernünftige Frage, was zur Automatisierung des Prozesses verwendet werden kann.


Wir git clone https://github.com/davidjamesca/ctypesgen.git Repository git clone https://github.com/davidjamesca/ctypesgen.git . Wechseln Sie zur zuvor erstellten Bibliothek build/lib/ und führen Sie das Skript aus.


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

Danach überprüfen wir die Arbeit.


 $ 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 

Um auf die Frage der bereits installierten Bibliotheken zurückzukommen, nehmen wir an, wir möchten einen Wrapper für eine Neonbibliothek erstellen (die auf bequeme Weise bereits auf dem System installiert ist), wie in der Readme-Datei Stypesgen gezeigt.


 $ 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'] 

Zum Schluss noch ein Link zu Github , wie könnte es ohne ihn sein.

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


All Articles