はじめに
Pythonで作ったnumpy.ndarrayをC++側で加工して、Pythonに返す方法を試します。
PythonからCの関数を呼び出す方法は色々あるようですが、
今回は、numpyを操作したいので、Boost C++ Librariesを使ってみます。
準備
Boost C++ Librariesをインストールします。
https://www.boost.org/doc/libs/1_67_0/more/getting_started/unix-variants.htmlの第1節に従ってダウンロードし、第5節に従ってビルドします。
方法(動的リンク版)
まず、C++のコードです。ファイル名は test1.cpp とします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | #include <boost/python/numpy.hpp>
namespace p = boost::python;
namespace np = boost::python::numpy;
void plus_scalar(np::ndarray &a, float b)
{
auto N = a.shape(0);
auto *p = reinterpret_cast<float*>(a.get_data());
for(auto v=p; v!=p+N; ++v){*v=*v+b;}
}
BOOST_PYTHON_MODULE(test1)
{
Py_Initialize();
np::initialize();
p::def("plus_scalar", plus_scalar);
}
|
Python側から第1引数で渡される1次元NumPy配列に第2引数で渡される値を足すだけの関数plus_scalarを定義しています。
このtest1.cppをコンパイルします($はシェルのプロンプトです)。
$ g++ --std=c++14 --shared -fPIC -I$BOOST_PATH/include/ -I/usr/include/python2.7/ test1.cpp -L$BOOST_PATH/lib -lboost_numpy27 -lboost_python27 -o test1.so
$BOOST_PATHはBoost C++ Librariesをインストールしたパスです。
ライブラリのパスが通っていないので、
$ export LD_LIBRARY_PATH=$BOOST_PATH/lib
を実行し、.soをロードできるようにしておきます。
これをPythonから呼び出します。次のようにPythonスクリプト t1.py を作成します。
| import sys
sys.path.append('.')
import test1
import numpy as np
a = np.array([1,2,3], dtype=np.float32)
print(a)
test1.plus_scalar(a, 3.0)
print(a)
|
そして、実行します。
python t1.py
すると、
[ 1. 2. 3.]
[ 4. 5. 6.]
が出力されます。確かに、3.0が足されています。
方法(静的リンク版)
動的リンク版では LD_LIBRARY_PATH を設定しましたが、使いにくいのでBoost関連のライブラリだけでも静的リンクしておきましょう。次のようにすることで.soを作ることができます。
g++ --std=c++14 --shared -fPIC -I$BOOST_PATH/include/ -I/usr/include/python2.7/ test2.cpp $BOOST_PATH/lib/libboost_numpy27.a $BOOST_PATH/lib/libboost_python27.a -o test2.so
もし、次のエラー
/usr/bin/ld: $BOOST_PATH/lib/libboost_numpy27.a(ndarray.o): 再配置 R_X86_64_32 (`.rodata.str1.8' に対する) は共有オブジェクト作成時には使用できません。-fPIC を付けて再コンパイルしてください。
$BOOST_PATH/lib/libboost_numpy27.a: error adding symbols: 不正な値です
collect2: error: ld returned 1 exit status
が出て、リンクできないときは、エラーメッセージの通りにBoostをビルドし直します。
具体的には、
[4]を参考に、
$ ./b2 --clear
$ ./b2 cxxflags=-fPIC cflags=-fPIC install
のように、cxxflagsを追加してビルドし直します。cflagsは不要かもしれません。なお、ここで追加したオプション名は、--cxxflagsでも、-cxxflagsでもなく、ハイフン無しの単なるcxxflagsです。
test2.cppは
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | #include <boost/python/numpy.hpp>
namespace p = boost::python;
namespace np = boost::python::numpy;
void plus_scalar(np::ndarray &a, float b)
{
auto N = a.shape(0);
auto *p = reinterpret_cast<float*>(a.get_data());
for(auto v=p; v!=p+N; ++v){*v=*v+b;}
}
BOOST_PYTHON_MODULE(test2)
{
Py_Initialize();
np::initialize();
p::def("plus_scalar", plus_scalar);
}
|
t2.pyは
| import sys
sys.path.append('.')
import test2
import numpy as np
a = np.array([1,2,3], dtype=np.float32)
print(a)
test2.plus_scalar(a, 3.0)
print(a)
|
です。test1がtest2に変わっただけです。
$ python t2.py
を実行すると、動的リンク版のときと同様に
[ 1. 2. 3.]
[ 4. 5. 6.]
と出力されます。
参考
[1] http://d.hatena.ne.jp/nihohi/20120306/1331002942
[2] http://tadaoyamaoka.hatenablog.com/entry/2017/05/25/234934
[3] https://stackoverflow.com/questions/10968309/how-to-import-python-module-from-so-file
[4] https://stackoverflow.com/questions/27848105/how-to-force-compilation-of-boost-to-use-fpic