Nim连接到Python
原文:https://akehrer.github.io/nim/2015/01/24/connecting-nim-to-python.html
在以前的文章中在最后询问了关于Nim连接Python接口的代码,在经过一些实验后我能够做一些工作了。所以,让我们一起看一看。
Compiling a library
首先我们要谈到的是Nim 编译器。在大多数情况下你把Nim代码编译成可执行文件要运行这个命令。
nim c projectfile.nim
但是要想把代码写到一个库中,我们将需要看编译器的文档了。这里有很多选择,但是 --app 是我们感兴趣的,在我们的例子中,我们将使用 -app 命令去创建一个共享库。(.dll
in Windows, .so
in linux, .dylib
in OSX).
nim c --app:lib projectfile.nim
运行以上的命令能够在 .nim 文件的目录下创建一个 projectfile.dll 或 libprojectfile.so 文件。这简直太棒了,但是它并没有给到我们想要的。这个库已经被创建,但是没有我们已经暴露出来的功能函数。不是非常有用。
The exportc
& dynlib
pragmas
Nim有特殊的方法访问 pragmas, 当它解析特殊的代码块时会得到编译器额外的信息。我们早在以前的文章中看到它们的使用,记得下面的代码从 math.h 中加载 isnan 函数。
import math proc cIsNaN(x: float): int {.importc: "isnan", header: "<math.h>".} ## returns non-zero if x is not a number
在上面的代码 {. and .} 之间包含 importc 语句,这是要告诉编译器在Nim 中的 cIsNaN 过程要用到 math.h 中的 isnan 函数。
所以,如果Nim 有导入C代码的方法,那么也应该有方法把代码导出到C中;它是exportc。用 exportc 能告诉编译器要显示我们的函数到外面。加上 dynlib 语句来确保能从访问库中访问我们的过程。
让我们从一些简单的例子开始:
proc summer*(x, y: float): float {. exportc, dynlib .} = result = x + y
保存它为 test1.nim,在Windows下运行命令 nim c --app:lib test1.nim 会得到一个 test1.dll 文件。现在让我们看看是否能用它。( 在linux下会生成libtest1.so文件)
Python ctypes
为了使用编译过的库,我们将会用到python 中的 ctypes 模块,从2.5版本后它已经是标准库的一部分了。它允许我们使用被编译在库中基于C的代码,要在Python 类型 与 C类型之间转换。这里的代码能访问我们的summer函数。
from ctypes import * def main(): test_lib = CDLL('test1') # Function parameter types test_lib.summer.argtypes = [c_float, c_float] # Function return types test_lib.summer.restype = c_float sum_res = test_lib.summer(1.0, 3.0) print('The sum of 1.0 and 3.0 is: %f'%sum_res) if __name__ == '__main__': main()
我们能够看到在加载了库后,设置了参数和函数的返回类型,然后传了两个参数调用这个函数。让我们看看发生了什么。
C:\Workspaces\nim-tests>python test1.py The sum of 1.0 and 3.0 is: 32.000008
嗯,这是不正确的。看起来我们可能没有使用正确的参数和返回类型。让我们比较一下Nim 的float型和Python ctypes 的 c_float 型。根据Nim 手册,float 型设置为最快处理器的浮点类型。Python ctypes 手册说 c_float 是与C中的float型一样。是因为我用32位版本的Nim 运行这段代码和2.7版本的Python 在64位windows 机器 是 Nim 编译器使它的 float 为 double?
from ctypes import * def main(): test_lib = CDLL('test1') # Function parameter types test_lib.summer.argtypes = [c_double, c_double] # Function return types test_lib.summer.restype = c_double sum_res = test_lib.summer(1.0, 3.0) print('The sum of 1.0 and 3.0 is: %f'%sum_res) if __name__ == '__main__': main()
C:\Workspaces\nim-tests>python test1.py The sum of 1.0 and 3.0 is: 4.000000
看起来已经解决了。当我们知道我们将使用 exportc 或者 创建一个共享库,Nim 有让我们添加更多约束的类型,减少这些类型的混乱(e.g. Cfloat,cint)。
openArray
arguments & the header file
现在让我们来尝试更复杂的事情,使用之前两篇文章写的 statistics 模块中的 median 函数。
Nim code:
proc median*(x: openArray[float]): float {. exportc, dynlib .} = ## Computes the median of the elements in `x`. ## If `x` is empty, NaN is returned. if x.len == 0: return NAN var sx = @x # convert to a sequence since sort() won't take an openArray sx.sort(system.cmp[float]) if sx.len mod 2 == 0: var n1 = sx[(sx.len - 1) div 2] var n2 = sx[sx.len div 2] result = (n1 + n2) / 2.0 else: result = sx[(sx.len - 1) div 2]
Python code:
1 from ctypes import * 2 3 def main(): 4 test_lib = CDLL('test1') 5 6 # Function parameter types 7 test_lib.summer.argtypes = [c_double, c_double] 8 test_lib.median.argtypes = [POINTER(c_double), c_int] 9 10 # Function return types 11 test_lib.summer.restype = c_double 12 test_lib.median.restype = c_double 13 14 # Calc some numbers 15 nums = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] 16 nums_arr = (c_double * len(nums))() 17 for i,v in enumerate(nums): 18 nums_arr[i] = c_double(v) 19 20 sum_res = test_lib.summer(1.0, 3.0) 21 print('The sum of 1.0 and 3.0 is: %f'%sum_res) 22 23 med_res = test_lib.median(nums_arr, c_int(len(nums_arr))) 24 print('The median of %s is: %f'%(nums, med_res)) 25 26 if __name__ == '__main__': 27 main()
有一些有趣的事情发生,这里允许我们调用 median 过程。在这个Nim代码中你能够看到它仅仅需要一个参数,一个float型的 openArray。像我们在第二篇文章中看到的,openArrays 使传送不同大小的数组给过程成为可能。但是怎样把它转化进C库中呢?(你可能会从python 代码入手)。一件事情可能会帮助我们,就是一个能够被传送到Nim编译器的额外的参数。
nim c --app:lib --header test1.nim
这个 --header 选项将会在模块被编译的文件夹 nimcache 中产生一个C头文件。如果我们看到头文件会像下面一样:
1 /* Generated by Nim Compiler v0.10.2 */ 2 /* (c) 2014 Andreas Rumpf */ 3 /* The generated code is subject to the original license. */ 4 /* Compiled for: Windows, i386, gcc */ 5 /* Command for C compiler: 6 gcc.exe -c -w -IC:\NIM\lib -o c:\workspaces\nim-tests\nimcache\test1.o c:\workspaces\nim-tests\nimcache\test1.h */ 7 #ifndef __test1__ 8 #define __test1__ 9 #define NIM_INTBITS 32 10 #include "nimbase.h" 11 N_NOCONV(void, signalHandler)(int sig); 12 N_NIMCALL(NI, getRefcount)(void* p); 13 N_LIB_IMPORT N_CDECL(NF, median)(NF* x, NI xLen0); 14 N_LIB_IMPORT N_CDECL(NF, summer)(NF x, NF y); 15 N_LIB_IMPORT N_CDECL(void, NimMain)(void); 16 #endif /* __test1__ */
重要的是第13和14行,我们两个输出的过程被唤起。能够看到 summer 是要两个NF类型的参数,我们可以假设为Nim中的float 型。另一方面median 不是像我们在Nim 过程中定义的一个参数,而是两个,一个是指向NF的指针,另一个是Nim 中的整型 NI。有一个关于整型的暗示是一个值的长度。所以这个 openArray 参数已经被转化为在C中标准的传递数组的方式,一个指针指向数组和一个数组的长度。
在Python代码中你能够看到我们设置了正确的参数 ([POINTER(c_double),c_int) 和返回类型(c_double) 和 在15行到18行我们把一个python列表映射到C的double 型数组中。然后当我们在23行调用这个函数,确保列表的长度转化为一个c_int。让我们检测一下结果。
C:\Workspaces\nim-tests>python test1.py The sum of 1.0 and 3.0 is: 4.000000 The median of [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] is: 4.500000
这看起来是好的,它对于这篇文章来说是足够的。在以后的文章中我们将看到更多类型的接口,像字符串,元组和自定义对象。
Reference
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。