C++のコードをPythonで呼ぶ

github.com

C++11のコードをPythonから利用できるようにするためのアプローチの1つに上のpybind11を使用する方法があるらしい。
今回はそれを実際に動かしてみる。

pybind11とは

C++のコードをPythonから利用できるようにするためのヘッダーライブラリ。
ヘッダーのみとなっているので使用する際のコンパイルとかは必要はないです。

このC++のコードをPythonから使えるよみたいなので使われるのが Boost.Python - 1.61.0 です。
pybind11のdocでも触れられてますが、Boost.Pythonは様々なコンパイルや古いシステムに対応するために複雑になっていて、pybind11はシンプルな構成となっているようです。

サポートされたコンパイラ環境としては

のようです。

準備

今回は Ubuntu16.04 で動かしていきました。

まずは動作に必要なものをいれていきます。
pybind11ではCMakeLists.txtが提供されているのでcmakeMakefileつくってmakeを実行します。

$ sudo apt-get install -y cmake
# cmake時にNo CMAKE_CXX_COMPILER could be found.と言われるので
$ apt-get install build-essential -y
# Could NOT find PythonInterp (missing: PYTHON_EXECUTABLE)と言われるので
$ apt-get install python3-dev python3-pip
$ cd [pybind11をcloneしたディレクトリ]
$ mkdir build
$ cd build
$ cmake ..
$ make check -j 4

やってみる

まずは今回使用するC++のコードがこちら
やりたいことは単純で与えられた2つの数値の和を求めるだけ

ファイル名はexample.cppで保存している。

#include <pybind11/pybind11.h>

int add(int i, int j) {
    return i + j;
}

PYBIND11_MODULE(example, m) {
    m.doc() = "pybind11 example plugin";

    m.def("add", &add, "A function which adds two numbers");
}

続いてPythonから呼び出すためにC++のコードをビルドする。
docにビルド時のサンプルが書いてあるのでincludeディレクトリとpython-configがpython3-configとかを考慮して下記で実行してみる。

$ c++ -O3 -shared -std=c++11 -I /root/pybind11-master/include `python3-config --cflags --ldflags` example.cpp -o example.so

すると下記のようなエラーが出て来る。

/usr/bin/ld: /tmp/ccT9sejz.o: relocation R_X86_64_32 against `.rodata.str1.8' can not be used when making a shared object; recompile with -fPIC
/tmp/ccT9sejz.o: error adding symbols: Bad value
collect2: error: ld returned 1 exit status

どうやら -fPIC オプションが足りないみたいなので追加する。

$ c++ -O3 -shared -std=c++11 -fPIC -I /root/pybind11-master/include `python3-config --cflags --ldflags` example.cpp -o example.so

無事ビルドが完了して example.so が生成された。
後はPythonのREPLで確認していく。

$ python3
Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> example.add(3, 4)
7

となる!!!!!!
おぉーでたという感じ。

せっかくなのでC++のコードを少し見てみる。

#include <pybind11/pybind11.h>

int add(int i, int j) {
    return i + j;
}

PYBIND11_MODULE(example, m) {
    m.doc() = "pybind11 example plugin";

    m.def("add", &add, "A function which adds two numbers");
}

ここでPYBIND11_MODULEの第一引数に渡されているexampleはモジュール名になる。
つまり PYBIND11_MODULE(hogehoge, m) { としてビルド後の生成ファイルを hogehoge.so とした時はPython側では import hogehoge となる。

m.doc() = "~~~" はそのままの意味でモジュールに対してのドキュメントを定義する。
これを定義しておけば

$ python3
>>> import example
>>> example.__doc__
pybind11 example plugin

このようにdocを参照できるようになる。
最後の m.def("add", &add, "A function which adds two numbers");Pythonから呼び出すメソッドの定義をしている。
これにもdocは定義できるので、Python上でdocを参照することが出来る。

>>> print(hhh.add.__doc__)
add(arg0: int, arg1: int) -> int

A function which adds two numbers