LoginSignup
29
28

More than 5 years have passed since last update.

Pybind11をパッケージ化するまでの流れ

Posted at

pybind11-logo.png

背景

Pythonのfor文は計算時間が長いという問題があり、そこを高速化したかったので、C++でアルゴリズムを書いて、pythonにインポートするということをしました。
その際、pybind11というパッケージを利用したのですが、まだあまり参考にできる記事が少なかったので、その際のメモを残したいと思います。

環境

OS: macOS Mojave 10.14.2
python: pyenv 3.6.5

Pybind11とは

公式github: https://github.com/pybind/pybind11
公式ドキュメント: https://pybind11.readthedocs.io/en/stable/

pybind11とは公式ドキュメントによると

pybind11 is a lightweight header-only library that exposes C++ types in Python and vice versa, mainly to create Python bindings of existing C++ code.

ということです。つまり、PythonにC++のコードを、またC++にPythonのコードを繋げることができるライブラリです。

実はこういったことを行ってくれる他の1つのライブラリもあり、その1つにBoost.Pythonというのがあるらしいです。が、自分は触ったことがないので、こちらの参考サイトのみ載せておきます。

Pybind11とBoostの大きな違いはBoostはとても大きいライブラリだということです。単純なモジュールを呼ぶためだけにBoostを使うのは非効率的ということもあり、Pybind11は作成されました。

その辺の詳しいベンチマークなどはこちらに詳しく書いてあるので、興味あればみてみてください!

インストール方法

インストール方法は何通りかあるのですが、今回自分はpipを使ったのでそちらの方の紹介をします。

$ pip3 install pybind11

この他にもgithubからのインストール方法もあるのですが、実行方法が異なってくる(コンパイルする際のコマンドが違うため)ので、気をつけた方がいいです!

実装例(簡易版)

背景にも書いたのですが、自分はpythonのfor文の計算量が多いのが問題となっていたので、配列を用いたモジュールを書きました。が、公式にも英語のサイトにも中々配列の使い方が綴られておらず(自分の裁量不足ということもあるのですが...)、試行錯誤しました。
その結果、動いたコードの簡略版で実装例を紹介します。

コード


#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

void print_arrays(std::vector<int> lst) {
  // 配列の長さ
  int length = sizeof(lst)/sizeof(lst[0]);

  // 配列の各要素を出力する
  for (int i=0; i < length-1; i++) {
    printf("%d\n", lst[i]);
  }
}

// PYBIND11_MODULE()はPythonでimportする際の関数を作成する
PYBIND11_MODULE(example, m) {
  m.doc() = "Example pybind11"; // optional module docstring
  m.def("print_arrays", &print_arrays, "A function that prints array elements.");
}

実行例

あとは実行するだけです!が、ここで私は躓きました。

失敗したコマンド

上は色んなサイトを調べた結果上手くいったコマンドです。
そして、実際に多くの人が行うように、私も公式ドキュメントに従って、以下のコマンドを実行をしました。

c++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` example.cpp -o example`python3-config --extension-suffix`

その結果、バーっとエラーが吐かれ(長すぎるため省略します)、最後に

ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

と言われてしまいました。

色々調べた結果、こちらのイシューが参考になりました。

成功したコマンド

そこで、上記のサイトを参考に、以下のコマンドで成功しました。

clang++ -O3 -Wall -shared -std=c++11 -fPIC `python -m pybind11 --includes` -undefined dynamic_lookup example.cpp -o example`python3-config --extension-suffix`

その結果、同じディレクトリにexample.cpython-36m-darwin.soという名前のファイルができたと思います。

詳しくは調べきれていないのですが、多分pyenvの影響かと思うので、使っている人は気をつけてください!

実行結果

ここまで来ればあとは簡単です。
先ほどのコード内でPYBIND_MODULE()という関数を用いて、pythonでインポートする際の関数を生成しました。

あとは、REPLで以下のようにすればいけます。

$ python3
>>> import example
>>> example.print_arrays([1, 2, 3, 4, 5])
1
2
3
4
5
>>>

応用

ここまでやると、本格的にライブラリを作りたくなってきました。
というのも、pybind11の公式ドキュメントにビルドシステムというセクションがあり、自分が作ったライブラリを実際にpipでインストールできるようになるまでが書かれていたからです(Pypiに載せるところまではやらないです)。

ディレクトリ構成

ディレクトリ構成は人それぞれの好みがあると思いますが、今回は公式ドキュメントでも紹介されていたgitのプロジェクトを参考にしました。

pybind11_example/
 ├ src/
 │ ├ addition.cpp
 │ ├ subtraction.cpp
 │ └ main.cpp
 ├ tests/
 │ └ test.py
 ├ setup.py
 └ README.md

src/内のコード

src内には任意のファイルを作って大丈夫です!

配列の各要素を足し合わせる関数を格納したファイル

addition.cpp
#include <pybind11/stl.h>

int sum_arrays(std::vector<int> lst) {
  // 配列の長さ
  int length = lst.size();
  int sum = 0;

  // 配列の各要素を出力する
  for (int i=0; i < length; i++) {
    sum += lst[i];
  }

  return sum;
}

配列の各要素の積を返す関数を格納したファイル

product.cpp
#include <pybind11/stl.h>

int product_arrays(std::vector<int> lst) {
  // 配列の長さ
  int length = lst.size();
  int pro = 1;

  // 配列の各要素を出力する
  for (int i=0; i < length; i++) {
    pro *= lst[i];
  }

  return pro;
}

上記のモジュールを束ねるファイルmain.cpp

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

#include "addition.cpp"
#include "product.cpp"

PYBIND11_MODULE(pybind11_example, m) {
  m.doc() = "Example pybind11"; // optional module docstring
  m.def("sum_arrays", &sum_arrays);
  m.def("product_arrays", &product_arrays);
}

ここで、今回はライブラリ名をpybind11_exampleと設定しました。これは後にpipでインストールする際に同じ名前を用いるので、気をつけてください。

setup.py

あとはsetup.pyを記述するだけです。(README.mdやtest/内は省略をします)
これはpybind11の公式ドキュメントにビルドシステムをかなり参考にしました。

基本真似をして、ext_modules=[]setup()の中身を書き換えるだけです。

setup.py
...
ext_modules = [
    Extension(
        'pybind11_example', // ライブラリ名
        ['src/main.cpp'], // 参照にするファイル
        include_dirs=[
            # Path to pybind11 headers
            get_pybind_include(),
            get_pybind_include(user=True)
        ],
        language='c++'
    ),
]
...
...
setup(
    name='pybind11_example', // ライブラリ名
    version=0.1,
    author='sff1019',
    description='A pybind11 example',
    long_description='',
    ext_modules=ext_modules,
    install_requires=['pybind11>=2.2'],
    cmdclass={'build_ext': BuildExt},
    zip_safe=False,
)

これで完成です!

実行

pybind11_example11の親ディレクトリに入ります。

$ pip install ./pybind11_example
Processing ./pybind11_example
...                                                                         
Successfully installed pybind11-example-0.1

となれば成功です。

あとは好きなところで

import pybind11_example

pybind11_example.sum_arrays([1,2,3])

のように実行ができます。
ちなみに、出力は以下のようになります。

6

最後に

最初のpyenvのところだけが突っかかりましたが、思った以上に簡単にC++のコードをpythonにインポートすることができるのだな、と感じました。

参考サイト

29
28
2

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
29
28