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 型设置为最快处理器的浮点类型。Pythonctypes手册说 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

Nim Compiler User Guide

Nim Manual

Python ctypes

Python ctypes Tutorial

(Thanks to dom96 for corrections.)

时间: 2024-08-08 22:44:07

Nim连接到Python的相关文章

通过python 构建一个简单的聊天服务器

构建一个 Python 聊天服务器 一个简单的聊天服务器 现在您已经了解了 Python 中基本的网络 API:接下来可以在一个简单的应用程序中应用这些知识了.在本节中,将构建一个简单的聊天服务器.使用 Telnet,客户机可以连接到 Python 聊天服务器上,并在全球范围内相互进行通信.提交到聊天服务器的消息可以由其他人进行查看(以及一些管理信息,例如客户机加入或离开聊天服务器).这个模型如图 1 所示. 图 1. 聊天服务器使用 select 方法来支持任意多个客户机 聊天服务器的一个重要

Python 点滴 III

[Python模块的角色] 代码重用 系统命名空间的划分 实现共享服务和数据 [import模块工作步骤] 在Python中,导入并非只是如C中#include一样:把一个文件插入另外一个文件.程序第一次导入时,会执行三个步骤. 1. 找到模块 2. 编译成位码(需要时) 3. 执行模块的代码来创建其所定义的对象 [模块搜索路径] 按执行的先后顺序 1. 程序的主目录 2. PYTHONPATH目录 3. 标准链接库目录 4. 任何.pth文件的内容 这四个组件组合起来就变成了sys.path

Python之路-模块和包

一.模块 1.定义:包含了Python定义和声明的文件,文件名就是模块名字加上.py后缀. import加载的模块分为四个类别: 1.Python编写的代码(.py文件) 2.已经被编译为共享库或DLL的C或C++扩展 3.包好一组模块的包 4.使用C编写并链接到Python解释器的内置模块 2. 导入模块执行过程: 1.执行源文件 2.产生一个源文件的全局名称空间 3.在当前位置拿到一个模块名,指向源文件的产生的名称空间. 3.导入模块的二种只用方式: 1.import导入模块示例: 2.fr

廖雪峰python摘录二轮2

1 >>> def set_age(self, age): # 定义一个函数作为实例方法 2 ... self.age = age 3 ... 4 >>> from types import MethodType 5 >>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法 6 >>> s.set_age(25) # 调用实例方法 7 >>> s.age # 测试结果 8

python之路——常用模块

 什么是模块? 常见的场景:一个模块就是一个包含了python定义和申明的文件,文件名就是模块名字加上.py的后缀. 但其实import加载的模块分为四个通用类别: 1.使用python编写的代码(.py文件) 2.已被编译为共享库或DLL的C或C++扩展 3.包好一组模块的包 4.使用C编写并链接到python解释器的内置模块 为什么要使用模块? 如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过pytho

Centos 6.9 自带Python 2.6.6 切换为2.7.13(or later)

目的: 系统默认运行python, 系统提示2.6.6,不满足练习环境-->切到2.7.x步骤1: find 命令查找所有的python相关文件#find / name='python*' 2. 进入原来2.6.x python的路径,查看哪些和2.6相关. ls python* -l 3. 删除2.6相关的python 绿色文件,如#mv /usr/bin/python #mv /usr/bin/python2.6* 4:wget在线或者离线下载python 2.7.13->解压 进入目录,

7行Python代码的人脸识别

随着去年alphago 的震撼表现,AI 再次成为科技公司的宠儿.AI涉及的领域众多,图像识别中的人脸识别是其中一个有趣的分支.百度的BFR,Face++的开放平台,汉王,讯飞等等都提供了人脸识别的API,对于老码农而言,自己写一小段代码,来看看一张图片中有几个人,没有高大上,只是觉得好玩,而且只需要7行代码. import cv2 face_patterns = cv2.CascadeClassifier('/usr/local/opt/opencv3/share/OpenCV/haarcas

python 3 包与模块

包与模块 一 .模块 1 什么是模块? 常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀. import加载的模块分为四个通用类别: 1 使用python编写的代码(.py文件) 2 已被编译为共享库或DLL的C或C++扩展 3 包好一组模块的包 4 使用C编写并链接到python解释器的内置模块 2 为何要使用模块 退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通

浅析 C++ 调用 Python 模块

浅析 C++ 调用 Python 模块 作为一种胶水语言,Python 能够很容易地调用 C . C++ 等语言,也能够通过其他语言调用 Python 的模块. Python 提供了 C++ 库,使得开发者能很方便地从 C++ 程序中调用 Python 模块. 具体的文档参考官方指南: Embedding Python in Another Application 调用方法 1 链接到 Python 调用库 Python 安装目录下已经包含头文件( include 目录)和库文件 ( Windo