如何创建Python包装器而不会发疯

最近,我在Habré上阅读了一篇有关非常有用的工具的文章,并且由于我一直在寻找某种项目来开始贡献,所以我决定查看github上的内容以及如何提供帮助。 一个问题是为C库创建一个包装器(我将在以后使用包装器)。 当时我想,“哦,有趣的事,我敢肯定,不会超过一个小时。” 我错了多少。


在本文中,我决定不展示一种解决类似问题的方法,而是展示几种不同的选择。 我将向您展示在Python中使用C进行编译,使用小型自写库C来创建模块的选项,以及-最后一个选择-在Python中使用无痛苦和pxd文件的大型C库。


赛顿


关于这方面的书籍已经写过,有很多文章,包括有关哈布雷的文章,所以我不会过多地关注安装或一些基本的东西。 在这里阅读更多


使用Cython,我们可以解决几个问题。 对于python中的C代码的某些实例,它通常非常适合并且部分解决了库导入的问题。


让我们看一下官方文档中的一个简单示例。


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

将此文件另存为fib.pyx
.pyx是Cython文件的一种特殊格式,类似于C代码的.c并包含一些功能。 还有.pxd ,在C中是.h ,其中包含对功能,结构等的描述。


为了以某种方式与fib函数进行交互,我们需要“编译”代码。 为此,请使用此内容创建setup.py


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

运行python3 setup.py build_ext --inplace您可以将其导入常规python中,并享受与 正常的 编译语言。


 import fib fib.fib(2000) 

但是在这里,我们编写了Python代码并将其转换为C,但是编写C代码并在Python中运行呢?


没问题 我们创建一个新文件夹,在内部创建lib文件夹,在其中创建lib/includelib/src ,实际上,使用C的每个人都已经知道其中的内容。 在主文件夹中,创建另一个python_wrap文件夹。


让我们struct.h lib/include并创建struct.h ,在其中我们将描述一个函数并了解如何通过Cython在C语言中使用结构。


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

让我们创建另一个文件,我们将其称为include.h ,它将具有另一个函数并从struct.h导入结构


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

现在我们将在文件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; } 

是的,我不假装变量名是原始的,但是我们几乎完成了C部分。 还有什么 添加一个Makefile或CMake。 在lib文件夹中,创建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}) 

在主目录中,我们需要指出我们在lib文件夹中有一个要编译的项目。 创建另一个CMakeLists.txt文件,但已在根目录下。


 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) 

在这里,我使用了一个小文件,该文件将Python包装器结构传输到构建目录,以便您可以编译Python文件。 如果您将相对路径传递到include目录和库所在的位置,则可能不需要这样做。 例如,如果该库已经被编译并安装在系统中,那么我们将设置系统目录的路径,但稍后会介绍更多。


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

在组装我们的项目之前,让我们看一下Python部分。 在python_wrap文件夹中, python_wrap创建了两个文件main.pxdmain.pyx 。 在main.pxd我们需要描述*.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); 

使用cdef extern from "include/include.h" ,我们指示我们将描述哪个文件。 接下来是ctypedef struct struct_test:对结构ctypedef struct struct_test:描述,以便可以从Python代码中使用它。 最后,实际上是对两个功能的描述。 我想指出,我们需要描述include.h中的所有include,如果它从另一个头文件导入结构和函数,我们认为所有这些都在一个文件中。


main.pyx我们编写了从Python到C的转换功能。这不是必需的,但是为什么要为C语言的结构加载Python代码。要创建结构,只需定义一个包含所有参数的字典就足够了。


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

现在,我们需要将它们整合在一起。 像以前一样,将setup.py文件添加到项目根目录。


 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) 

为了编译C代码并编译我们的库,我们将创建一个简单的bash脚本。


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

我们启动并检查


 $ 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


前面的示例非常简单明了,但是如果您需要包装一个非常大的库,并用很长的时间写所有的.pxd文件又很困难,那该怎么办呢,所以有一个合理的问题,可以用什么来使过程自动化?


我们git clone https://github.com/davidjamesca/ctypesgen.git存储库git clone https://github.com/davidjamesca/ctypesgen.git 。 转到先前构建的库build/lib/并运行脚本。


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

之后,我们检查工作。


 $ 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 

好了,回到已经安装的库的问题,让我们说我们要在一个霓虹灯库上包装(该包装已经以任何方便的方式安装在系统上),如自述文件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'] 

最后,指向github的链接,没有它怎么可能。

Source: https://habr.com/ru/post/zh-CN467615/


All Articles