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 64windows 机器 是 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__ */

        重要的是第1314行,我们两个输出的过程被唤起。能够看到 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

Nim Compiler User Guide
Nim Manual
Python ctypes
Python ctypes Tutorial 

(Thanks to dom96 for corrections.)



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值