Help us understand the problem. What is going on with this article?

pybind11でC++の関数をpythonから使う

More than 3 years have passed since last update.

この記事は

Python Advent Calendar 2016 8日目です。

今年のBoost.勉強会でpybind11の話をしたのですが、あまり具体的なところに触れられなかったので、その補足でもあります。

pybind11とは

C++11以降で使えるC++のpython bindingsです。C++で作成したモジュールをpythonに公開することができます。時間のかかる処理はC++、色々試したい場合はpython、と使い分けしたい人にオススメ。Numpyをうまく使おうとしても限界があるのでC++とかで書かなければならない部分はどうしてもあります。私は最近、自作しなけばならない最低限だけC++で書いて、残りはscipy、scikit-imageなどを利用ってスタイルです。Cythonとかの選択肢もありますけど、自動生成されたCのコード追いかけてデバッグするくらいならC++のソースの方がまだ楽。

PythonとC++のbindingsはいくつかありますが、その1つとしてBoost.Pythonがあります。これそのものは機能としては便利です。Boost.Pythonについては以前にも書いた。(ただしC++からPythonを使う話)

Pythonが科学技術計算界隈でよく使われる理由の1つにNumpyの存在があるのですが、Boost.Pythonを利用したライブラリにBoost.Numpyというものがあって、これを使うとC++の関数にNumpyで作ったデータを簡単に渡せたりするのでとても便利です。これも以前書いた

しかし、Boost.Pythonはライブラリをビルドする必要があるのでめんどくさいです。誤解されることもあるのですが、Boost.PythonはPython2系でもPython3系でも使えます。ただし、Python2系で使うためには2系のビルドを、Python3系で使うためには3系のビルドをしなきゃならない。デバッグ版とか静的リンクか動的リンクかも選ばなければならない。もちろんBoost.Numpyもあわせてビルドが必要。

そんなめんどくさいBoost.Pythonの代わりとなるのがpybind11です。

Boost.Pythonとの違い

  • C++11以降のコンパイラが対象
  • Header-onlyで使える

C++11をサポートしていないコンパイラでは使えません。そんなコンパイラは投げ捨てましょう。Boost.Pythonは古いコンパイラでも動くようにしてるため、肥大化してますが、pybind11はC++11以降のみを対象としてるので小さくまとまってます。

Boost.Pythonはライブラリのビルドが必要でインストールも面倒でしたが、pybind11はHeader-onlyなのですぐに使えます。この利点だけでもpybind11に移行する理由になると思います。

Boost.Pythonからの移行は比較的楽です。C++11が使えるなら今すぐ検討しましょう。

EigenやNumpyとの連携

C++の行列ライブラリEigenや、Pythonの配列ライブラリNumpyとの連携がすでに考慮されてます。Boost.PythonみたいにBoost.Numpyを別途インストールする必要はありませんし、ライブラリをビルドする必要もありません。

インストール

Header-onlyなのでgithubからcloneまたはダウンロードして好きなディレクトリに置くだけ。OSのパッケージマネージャとかでもインストールできることがありますが、頻繁に更新されてるみたいなのでgithubからとってきた方がいいと思います。私はパッケージマネージャでインストールしたバージョンが古くて、最新版ならあるはずの関数がなかったとか経験しました。

簡単な例

C++で作成した足し算を行う関数を、Pythonから利用してみます。公式ドキュメントに載っているものと同じ。

C++ソースコード

cppmod.cpp
#include<pybind11/pybind11.h>

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

PYBIND11_MODULE(cppmod, m) {
    m.doc() = "pybind11 example plugin";
    m.def("add", &add, "A function which adds tow numbers");
}

共有ライブラリにするので、main関数はありません。PYBIND11_PLUGINで指定した名前がモジュール名です。つまりPythonから使う場合、この例だとimport cppmodとします。

次のpy::moduleはモジュールの説明。名前と、いわゆるdocstringです。続いて、pythonに公開する関数を指定。最後のreturn m.ptr()はBoost.Pythonでは不要でしたが、pybind11では付けてください。

参考までに、Boost.Pythonの場合は

BOOST_PYTHON_MODULE(cppmod) {
    Py_Initialize();
    py::def("add",add); 
}

となります。ほとんど同じです。

コンパイル

共有ライブラリ(Windowsの場合はDLL、ただし拡張子は.pyd)としてコンパイルします。私はSConsを使ってるので次のような設定ファイルになります。環境に合わせて設定してください。ちなみにSConsはPythonでかけるビルドツールです。巨大プロジェクトだと遅いと言われてますが、C++のコンパイルそのものが遅かったりするので私は気にしないで使ってますね。。 流行には逆らえずcmakeを使うことにしました。CMakeLists.txtに次のようなことを書きます。

CMakeLists.txt
cmake_minimum_required(VERSION 3.0)

project(cppmod)
set(PYBIND11_CPP_STANDARD -std=c++14)
set(CMAKE_CXX_FLAGS "-Wall -Wextra -O2")
add_subdirectory(pybind11)

pybind11_add_module(cppmod SHARED cppmod.cpp)  

ビルドコマンドはこんなかんじです。

$ mkdir build && cd build
$ cmake ..
$ make

Pythonから実行

まず、コンパイルしてできた共有ライブラリ(例cppmod.cpython-36m-x86_64-linux-gnu.so)を、Pythonを実行するディレクトリに置きます。

次に、Pythonを起動してimportします。まずは、helpを出して見ます。次のようになるはずです。

input
>>> import cppmod
>>> help(cppmod)
output
Help on module cppmod:

NAME
    cppmod - pybind11 example plugin

FUNCTIONS
    add(...) method of builtins.PyCapsule instance
        add(int, int) -> int

        A function which adds two numbers

FILE
    /path/to/cppmod.so

モジュール、関数の説明(docstring)、関数の引数と返値の型などが出ます。

次に、add関数を使ってみます。

>>> cppmod.add(2,3)
5

C++で作成した関数を呼び出せました。

Numpyとの連携

pybind11/numpy.hヘッダを使います。py::array_tというクラスでndarray型のデータを受けとったりPythonに渡したりできます。

#include<pybind11/pybind11.h>
#include<pybind11/numpy.h>
#include<algorithm>
namespace py = pybind11;

auto npadd(py::array_t<double> arr, const double v) {
    const auto size = arr.size();
    py::array_t<double> ret(size);
    std::transform(
        arr.data(),arr.data()+size,
        ret.mutable_data(), [v](auto e) { return e+v; });
    return ret;
}

PYBIND11_MODULE(cppmod, m) {
    m.doc() = "pybind11 example plugin";
    m.def("npadd", &npadd, "A function which adds scalar to ndarray");
}

使用例

>>> import cppmod
>>> import numpy as np
>>> x = np.arange(10)
>>> cppmod.npadd(x, 3.)
array([  3.,   4.,   5.,   6.,   7.,   8.,   9.,  10.,  11.,  12.])

とても簡単ですね。

難点

ドキュメントはあるのですが、個々の関数のreferenceがまだないようです。ソースコード見ればいいので問題ないですが、問題ないのはC++チョットデキル人だけかもしれません。

おわり

pybind11便利なのでみんな使いましょう。

ignis_fatuus
C++とPythonを主に使ってます。あとFortranも。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away