PythonでCの関数を呼べたのでメモを書きます。
Cのラッパーを作るときはいくつかの方法がありますが、今回はBoost.Pythonを選びました。
インストール
この記事を参考しインストールしました。
自分的な手順は以下通りです。(Mac環境、Python3.7)
1. brewでBoost.Pythonをインストール
brew install boost-python3
2. シンボリックリンク
インストールしたら /usr/local/Cellar/
にある Boost.PythonのdylibとPython自体を実行するディレクトリにリンクします。
ln -s /usr/local/Cellar/boost-python3/1.71.0_1/lib/libboost_python37.dylib /path/to/directory/libboost_python37.dylib
ln -s /usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/Python /path/to/directory/libpython3.7.dylib
3. pyconfig.h
がincludeできないとエラーが出た時の対処法
最初にコンパイルしたときに、
fatal error: pyconfig.h: No such file or directory
が出たので以下の手順で解決できました。
(1)Pythonのincludesのパスの確認
python3.7-config --includes --libs
(2)下記のような情報が出てきます。一番目を選びます(この例は -I/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/include/python3.7m
)
-I/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/include/python3.7m -I/home/victor/anaconda3/include/python3.7m -lpython3.7m -lpthread -ldl -lutil -lrt -lm
(3) -I
を除いたパスで ~/.bashrc
を以下の行を追加します:
export CPLUS_INCLUDE_PATH="$CPLUS_INCLUDE_PATH:/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/include/python3.7m"
(4) 反映させます
source ~/.bashrc
Hello World
Boost.PythonはC++のライブラリなので、Cを使うときには、別途でC++を用意してCの内容をincludeします。
C、C++、PythonのファイルとMakefileを用意します:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void hello_world();
#include "my_c.h"
void hello_world() {
printf("hello world\n");
}
#include <boost/python.hpp>
extern "C" {
#include "my_c.h"
}
using namespace boost::python;
BOOST_PYTHON_MODULE(my_wrapper){
def("hello_world", hello_world);
}
import my_wrapper
my_wrapper.hello_world()
wrapper: my_cpp.cpp
gcc -c -o my_c.o my_c.c
g++ -c -o my_cpp.o my_cpp.cpp
g++ -fPIC -Wall -O2 -shared -o my_wrapper.so my_c.o my_cpp.o libboost_python37.dylib libpython3.7.dylib
これでできます
$ make
$ python my_python.py
hello world
CのstructやarrayとPythonのdictやlistの変換
シンプルな関数だと特に別の処理がいりませんが、お互いに知らないtypeだと変換は必要そうです。
説明の代わりにサンプルコードになります。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int a;
int b;
}my_struct;
my_struct my_sum(my_struct *my_array, int len);
#include "my_c.h"
my_struct my_sum(my_struct *my_array, int len) {
my_struct sum = { 0 };
for(int i = 0; i < len; i++) {
sum.a += my_array[i].a;
sum.b += my_array[i].b;
}
return sum;
}
#include <boost/python.hpp>
extern "C" {
#include "my_c.h"
}
using namespace boost::python;
// boost::python::dict, boost::python::list, boost::python::lenなどがあります
dict wrapper_sum(list py_list) {
int length = len(py_list);
my_struct ms[length];
for(int i = 0; i < length; i++) {
dict py_dict = extract<dict>(py_list[i]);
ms[i].a = extract<int>(py_dict["a"]);
ms[i].b = extract<int>(py_dict["b"]);
}
dict py_res;
my_struct c_res = my_sum(ms, length);
py_res["a"] = c_res.a;
py_res["b"] = c_res.b;
return py_res;
}
BOOST_PYTHON_MODULE(my_wrapper){
def("my_sum", wrapper_sum);
}
import my_wrapper
data = [
{
"a": 3,
"b": 9
},
{
"a": 1,
"b": 3
},
{
"a": 6,
"b": 2
},
{
"a": 0,
"b": 4
}
]
res = my_wrapper.my_sum(data)
print(res)
実行します:
$ make
$ python my_python.py
{'a': 10, 'b': 18}
結論
少し工夫すれば、速度のためにCで書いた関数は、
Pythonで書き直せずに使えたのはすごく嬉しかったです。