LoginSignup
8
6

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-09-06

  本日は

仕事のピークがすぎた後、また忙しくなるっぽいんで久々に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 で書くというコンビができます。

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6