Edited at

Nim を Pythonのctypes 経由で Python から呼び出す。(NumPy渡せたよ)


  本日は

仕事のピークがすぎた後、また忙しくなるっぽいんで久々にQiita書きます。

今日はPython から Nim を呼び出してみましょう。

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

でも簡単なやりとりはできますが、Numpyを突っ込ませることができませんでした。今回は NumPy も引数に渡す方法を紹介します。

次の記事、フォーラムを参考にしました。


使う道具


  • Nim

Ubuntu, Mac では choosenim を使うのが一番楽ですね。

choosenimでnim環境構築

が参考になると思います。


方針


  1. Nim はコードを C に変換してそのコードをCのコンパイラーに委ねます。
    ということは、Nimの関数に対応するCのコードの関数を見つけてそれの入出力を合わせてあげればいいわけです。

  2. Nim のコンパイラオプションとして、Cのコードを共有ライブラリにさせることができます。

  3. 一方で、Python は ctypes という標準ライブラリを持っています。と言うことは上で作成した共有ライブラリをロードすればいいわけです。


やってみよう

入力が配列になるような適当な関数を記述してみます。


do_something.nim

proc do_something*(xs:openArray[float], ys:openArray[float]):float {.exportc, dynlib.}=

var s:float=0.0
for i in low(xs)..high(xs):
s += xs[i]+ys[i]
echo "s = ", s
s

2つ配列を受け取って要素を全部足します。中身のセンスの良し悪しは別にして

これをコンパイルします。

$ nim c -d:release --app:lib do_something.nim

そうすると(Macで書いているので) libdo_something.dylib が出来上がります

これをPythonから呼び出します。


main.py

from ctypes import *

import numpy as np

logging.basicConfig()
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def main():
logger.info("load library")
test_lib = cdll.LoadLibrary("libdo_something.dylib")
np_array = np.array([1, 2, 3, 4, 5, 6, 7, 8]).astype(np.float64)
test_lib.do_something.argtypes = [np.ctypeslib.ndpointer(flags="C"), c_int,
np.ctypeslib.ndpointer(flags="C"), c_int]
test_lib.do_something.restype = c_double
res = test_lib.do_something(np_array, 8, np_array, 8)
logger.info(res)

if __name__ == '__main__':
main()


実行例は次の通り

$ python main.py

INFO:root:load library
s = 2.0
s = 6.0
s = 12.0
s = 20.0
s = 30.0
s = 42.0
s = 56.0
s = 72.0
INFO:root:72.0

期待していた通り 72 が出ました。


注意したいこと

Python から呼び出す関数はNimで書いたコードになかった引数が追加されます。Nimに渡す各々の配列の長さを入れる必要があります。

test_lib.do_something.argtypes = [np.ctypeslib.ndpointer(flags="C"), c_int,

np.ctypeslib.ndpointer(flags="C"), c_int]

Nim が吐き出すCの外部から見える関数のインターフェースはヘッダーをみると良いです。

Nim のコンパイルオプションを調べるとそれが可能と言うことがわかります。

Nim Compiler User Guide


--header:FILE | the compiler should produce a .h file (FILE is optional)


といふことで、--header オプションを追加して再コンパイル

nim c -d:release --header --app:lib do_something.nim

./nimcache/do_something.h を確認すると次のような記述が見えると思います。

N_LIB_IMPORT N_CDECL(NF, do_something)(NF* xs, NI xsLen_0, NF* ys, NI ysLen_0);

xsLen_0ysLen_0 が見えますね。これが配列の要素数に相当します。

NFNI~/.choosenim/toolchains/nim-0.18.0/lib/nimbase.h にて定義されてます。

ヘッダーを除けば雰囲気をつかめるのでそこから類推して関数の引数、戻り値の型を設定してあげませう。


 まとめ

PythonからNimを呼び出せました。引数でNumPyが使えます。

これで上位の部分をPythonで書いてスピードが必要な部分は Nim で書くというコンビができます。