Python3
Nim
nimpy

PythonからNimを nimpy 経由で呼び出す.

 本日は

Nim のお勉強です.

PythonからNimを呼び出したいなーというモチベーションで検索すると nimpy というモジュールに出会いました.

動作環境

Macbook12 inch
Nim:Version 0.18.0
Python: Python 3.5.5 (Miniconda)

簡単な例

簡単なNimの関数を呼び出す方法はすでに紹介されています.
- PythonでNimの関数を使う

フィボナッチ数列を実装した Nim のコードをライブラリとして作成し ctypes で呼び出す感じです.私の環境でも動きました.

欲を言うと array, seq を入出力として扱いたい. 

失敗例

Nim -> C -> Python の順で作れればいいのかな?ということで

を参考にしてみようかなと次のようなコードを作りました.

fib.nim
proc fib(a: cint): cint {.exportc.} =
  if a <= 2:
    result = 1
  else:
    result = fib(a - 1) + fib(a - 2)

proc get_seq(n: cint): ptr seq[cint] {.exportc.} =
  var v = newSeq[cint](n)
  for i, _ in v:
    var ci:cint = cast[cint](i)
    v[i]=ci
  #echo v
  result = cast[ptr seq[cint]](addr v[0])

proc get_arr(): ptr array[3, cint] {.exportc.} =
  var v : array[3,cint]
  for i, _ in v:
    var ci:cint = cast[cint](i)
    v[i]= -ci
  #echo v
  result = cast[ptr array[3, cint]](addr v[0])
maths.c
#include "fib.h"
#include <stdio.h>

int main(void)
{
    NimMain();
    auto seq = get_seq(3);
    auto arr = get_arr();
    for (int f = -10; f < 10; f++) {
        //printf("Fib of %d is %d\n", f, fib(f));
        printf("seq %d\n", *(seq + f));
        printf("arr %d\n", *(arr + f));

    }
    return 0;
}

-I オプションにて Nimの lib の場所を指定してコンパイルします.私の場合だとこんな感じ.
Nimは choosenim を経由してインストールしています.

$ nim c --noMain --noLinking --header:fib.h fib.nim 
$ g++ -Inimcache -I/Users/terasakisatoshi/.choosenim/toolchains/nim-0.18.0/lib nimcache/*.c maths.c 

実行するとこんな感じになります.

seq 48
arr 48
seq 0
arr -482461376
seq 0
arr 32766
seq 0
arr -482461632
seq 3992
arr 32766
seq 48
arr 345767982
seq 4
arr -1183022881
seq 210061664
arr 345767982
seq 3
arr -1183022881
seq 3
arr 0
seq 0
arr 0
seq 2
arr 0
seq 0
arr 0
seq 0
arr 0
seq 0
arr 0
seq 0
arr -482461328
seq 0
arr 32766
seq 0
arr 209977670
seq 0
arr 1
seq 0
arr 0

なるほどわからん.一旦諦めます.

ちょっと調べる

Pymod

Gitのリポジトリで Pymod というのを見つけました.2,3年間メンテナンスが止まっているようでサンプルを動かして見ましたが, うまく動作しませんでした.

nimpy

nimpy というのを見つけました. 

nimpy を使って見ます.

install

$ nimble install nimpy

 使用例

次のようにNimのコードを作ります.

mymodule.nim
# mymodule.nim
import nimpy

proc greet(name: string): string {.exportpy.} =
    return "Hello, " & name & "!"

proc get_seq(n: int): seq[int] {.exportpy.} =
  var v = newSeq[int](n)
  for i, _ in v:
    v[i]=i
  result = v

proc get_arr(): array[3, int] {.exportpy.} =
  var v : array[3,int]
  for i, _ in v:
    v[i]= -i
  result = v

proc double_list(int_list: seq[int]): seq[int] {.exportpy.}=
  var ret = newSeq[int](int_list.len)
  for i, v in int_list:
    ret[i]=2*v
  result = ret

Readmeに従って次ようにビルドします:

nim c --threads:on --tlsEmulation:off --app:lib --out:mymodule.so mymodule

こうすると mymodule.so が生成されるので python 側からは

import mymodule

という形で使用できます.

test.py
# test.py
import mymodule
print(mymodule.greet("Hi"))
print(mymodule.get_seq(3))
print(mymodule.get_arr())
print(mymodule.double_list([3, 2, 1]))

実行結果は次の通り

$ python test.py
Hello, Hi!
[0, 1, 2]
[0, -1, -2]
[6, 4, 2]

できました.

できていないこと

  • mymodule.double_list の引数には [1,2,3] とリストを渡していますが, タプル (1,2,3) はダメでした.

  • np.array([1,2,3]) は残念ながらできません.

とはいえ, 簡単な配列の受け渡しをPythonで出来る方法がわかりましたので今日は満足です.