# 本日は
仕事のピークがすぎた後、また忙しくなるっぽいんで久々にQiita書きます。
今日はPython から Nim を呼び出してみましょう。
PythonからNimを nimpy 経由で呼び出す.
でも簡単なやりとりはできますが、Numpyを突っ込ませることができませんでした。今回は NumPy も引数に渡す方法を紹介します。
次の記事、フォーラムを参考にしました。
使う道具
- Nim
Ubuntu, Mac では choosenim を使うのが一番楽ですね。
choosenimでnim環境構築
が参考になると思います。
- Python, NumPy, ctypes
方針
- Nim はコードを C に変換してそのコードをCのコンパイラーに委ねます。
ということは、Nimの関数に対応するCのコードの関数を見つけてそれの入出力を合わせてあげればいいわけです。 - Nim のコンパイラオプションとして、Cのコードを共有ライブラリにさせることができます。
- 一方で、Python は ctypes という標準ライブラリを持っています。と言うことは上で作成した共有ライブラリをロードすればいいわけです。
やってみよう
入力が配列になるような適当な関数を記述してみます。
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から呼び出します。
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 のコンパイルオプションを調べるとそれが可能と言うことがわかります。
--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_0
と ysLen_0
が見えますね。これが配列の要素数に相当します。
NF
や NI
は ~/.choosenim/toolchains/nim-0.18.0/lib/nimbase.h
にて定義されてます。
ヘッダーを除けば雰囲気をつかめるのでそこから類推して関数の引数、戻り値の型を設定してあげませう。
# まとめ
PythonからNimを呼び出せました。引数でNumPyが使えます。
これで上位の部分をPythonで書いてスピードが必要な部分は Nim で書くというコンビができます。