目次
- 目次
- はじめに
- pybind11とは?
- シンプルなサンプルコードの実行方法
- デフォルト引数
- C++コードの変数をpython側で利用する
- C++クラスのバインディング
- 継承
- 関数のオーバーロード
- Enum
- STLのコンテナのやり取り
- pybind11がサポートしているコンパイラ
- サンプルコード
- より詳しいPythonとCコードの連携方法を学びたい人は
- 参考資料
- MyEnigma Supporters
はじめに
Python Advent Calendar 2016の17日目の記事です。
何かのシステムを作る時に、
はじめはPythonでプロトタイプを作り、
pandasで入力データを流し込みつつ、
matplotlibで結果を確認しながら、
システムを作ることが多いです。
その後、そのシステムのより最適化するために、
C++に移植したりすることがあるのですが、
システムをいきなりPythonからC++に移植するのではなく、
システムの一部をC++に移植して、
残りのPythonシステムはそのままで実行したい時があります。
これにより、元のシステムからの移植の際に
バグが混入していないかが、確認しやすくなったり、
どの部分をC++に変えることで計算が早くなるのかが
わかりやすくなります。
そこで今回は
C++11のコードをPythonから利用できるようにする
pybind11というツールの概要と、
いくつかのサンプルを紹介したいと思います。
pybind11とは?
pybind11は、C++コードとPythonを連携させるための、
軽量ライブラリです。
似たツールとしては、Boost.Pythonがありますが、
Boost.Pythonは様々なコンパイラや、古いシステムに対応するために、
かなり複雑な構造になっているという問題があります。
pybind11は、よりシンプルな構成になっており、
依存関係も通常のPythonとC++の標準ライブラリのみで構成されており、
ヘッダーライブラリなので、事前のビルド等が不要で、
簡単に使うことができます。
pybind11のドキュメントはこちらになります。
pybind11 — Seamless operability between C++11 and Python — pybind11 2.3.dev0 documentation
シンプルなサンプルコードの実行方法
実際に、上記のドキュメントにある
サンプルコードの実行の仕方を説明します。
1. pybind11をDL
Githubのリポジトリから、
ソースコードをDLします。
ヘッダライブラリなので、
特にライブラリのビルドなどは不要です。
2. c++のコードを書く
まず初めに、Pythonから呼び出したいC++のコードを書きます。
今回は下記のような、シンプルな足し算の関数をhoge.hという
ヘッダライブラリに記述しました。
int add(int i,int j){ return i+j; }
3. pybind11のバインドc++コードを書く。
続いて、上記のコードをラッピングし、
pythonから実行可能にするために、
バインドC++コードを書きます。
#include <pybind11/pybind11.h> #include "hoge.h" namespace py = pybind11; PYBIND11_PLUGIN(example){ py::module m("example", "pybind11 example plugin"); m.def("add", &add, "test1"); return m.ptr(); }
pybind11のヘッダと、先程のコードをインクルードして、
pybind11の関数を使って、pythonモジュールを設定します。
上記のコードにおいて、exampleの部分はモジュール名になり、
moduleコンストラクタの引数は、モジュールのdoc stringになります。
def関数は、C++の関数をbindする関数です。
pythonから呼ぶ関数名と、関数のdocstringを設定しています。
最後の行は、モジュールのpythonオブジェクトを
pythonコードに渡す関数です。
4. C++コードをコンパイルする。
続いて、pythonからモジュールを参照できるように、
先程のC++コードをコンパイルします。
公式ドキュメント通りにコンパイルしてみたのですが、
自分の環境では、いくつか問題が出たのでメモしておきます。
Macの場合
ドキュメント通り、下記のコマンドでコンパイルできたのですが、
$ c++ -O3 -shared -std=c++11 -I ../../pybind11/include/
python-config --cflags --ldflags
example.cpp -o example.so
Macのシステム用のPython以外でコンパイルすると、
import時にエラーが出てしまいました。
pyenvでpythonの仮想環境を作っているのですが、
2.7.xでも3.5.xでもエラーが出ていました。
解決法を探している最中です。
ubuntuの場合
下記のコマンドのように、
-fpicというフラグを追加し、
-Wstrict-prototyesという
ワーニングメッセージを無くすために、
sedを使って、python configのコマンドを修正しています。
$ c++ -O3 -shared -std=c++11 -fpic -I ../../pybind11/include/
python-config --cflags --ldflags | sed s/-Wstrict-prototypes//
example.cpp -o example.so
上記のコマンドで問題なく、
python用のオブジェクトファイルができました。
5. pythonコードで読み込ませる
先程のコンパイルで、example.soというファイルが出来た場合、
(windowsでは example.pyd)
あとはpythonコードから、importすれば、
C++の関数を実行できます。
#! /usr/bin/python # -*- coding: utf-8 -*- import example print(example.add(5, 1))
標準出力に、6という値が表示されるはずです。
デフォルト引数
デフォルト引数を設定する場合は、
下記のように、c++のデフォルト引数を設定し、
int add(int i=1,int j=2){ return i+j; }
pyhonのバインディング側にも、
同じデフォルト引数を指定する必要があります。
m.def("add", &add, "test1", py::arg("i") = 1, py::arg("j") = 2);
あとは、python側で特に引数を指定しない場合は、
デフォルト引数が使われるようになります。
C++コードの変数をpython側で利用する
C++側のコードの中に、
下記のようなグローバル変数があり、
int NUMBER=5; float NUMBER2=5.2; std::string str="binding";
この変数の値を、モジュールの変数として
利用したい場合は、bindingコードの中で、
attr関数とcast関数を利用します。
PYBIND11_PLUGIN(example){ py::module m("example", "pybind11 example plugin"); m.attr("NUMBER") = py::cast(NUMBER); m.attr("NUMBER2") = py::cast(NUMBER2); m.attr("str") = py::cast(str); return m.ptr(); }
attr関数は、モジュールに変数を登録し、
cast関数はpythonのオブジェクトにデータをcastする関数です。
あとは、python側で下記のようにモジュールの変数にアクセスできます。
import example print(example.NUMBER) print(example.NUMBER2) print(example.str)
C++クラスのバインディング
続いて、C++のクラスのバインディングをしてみたいと思います。
下記のような、クラスがPet.hというコードに書かれているとします。
#include<string> class Pet { public: Pet(const std::string &name) : name(name){ } void setName(const std::string &name_){ name = name_; } const std::string &getName() const { return name; } static std::string getClassName(){ return "Pet"; } std::string name; };
上記のコードをbindingする
C++コードは下記のようになります。
テンプレート化されたclass_というAPIに対して、
def関数で関数を追加して行けばOKです。
py::initという関数はコンストラクタを設定する関数です。
static関数の場合は、def_staticという特別な関数になります。
/** * @brief sample code of pybind11 */ #include <pybind11/pybind11.h> #include "Pet.h" namespace py = pybind11; PYBIND11_PLUGIN(cpplib){ py::module m("cpplib", "pybind11 example plugin"); py::class_<Pet>(m, "Pet") .def(py::init<const std::string &>()) .def("setName", &Pet::setName) .def("getName", &Pet::getName) .def_static("getClassName", &Pet::getClassName); return m.ptr(); }
あとは、前述の方法でコンパイルすることで、
下記のように
pythonのクラスのように利用することができます。
#! /usr/bin/python # -*- coding: utf-8 -*- import cpplib pet = cpplib.Pet("pochi") print(pet.getName()) pet.setName("tama") print(pet.getName()) print(pet.getClassName())
継承
続いてC++の継承コードをバインディングする方法を説明します。
下記のようなPetクラスがあり、
#include <string> class Pet { public: Pet(const std::string &name) : name(name) {} std::string name; };
このペットクラスを継承したDogクラスが下記のように
存在していたとします。
#include "Pet.h" #include <string> class Dog : public Pet { public: Dog(const std::string &name) : Pet(name) {} std::string Bark() const { return "wan"; } };
上記の継承コードをバインディングする場合は、
下記のようにbindingコードを書きます。
class_ APIの2つ目に親クラスを設定することで
継承クラスをバインディングできます。
/** * @brief sample code of pybind11 */ #include "Dog.h" #include <pybind11/pybind11.h> namespace py = pybind11; PYBIND11_PLUGIN(cpplib) { py::module m("cpplib", "pybind11 example plugin"); py::class_<Pet>(m, "Pet") .def(py::init<const std::string &>()) .def_readwrite("name", &Pet::name); py::class_<Dog, Pet>(m, "Dog") .def(py::init<const std::string &>()) .def("Bark", &Dog::Bark); return m.ptr(); }
上記のコードをライブラリ化すると、
下記のようにPythonからライブラリを利用することができます。
#! /usr/bin/python # -*- coding: utf-8 -*- import cpplib pet = cpplib.Pet("pochi") print(pet.name) pet2 = cpplib.Dog("shiro") print(pet2.name) print(pet2.Bark())
関数のオーバーロード
PythonにはC++で言う所の関数のオーバーロードは難しいですが、
pybind11を使えば、C++コードの関数オーバーロードを、
pythonで利用することが可能です。
下記のような、
関数オーバーロードがあるC++クラスがあったとして、
#include <string> class Pet { public: Pet(const std::string &name, int age) : name_(name), age_(age) {} void Set(const std::string &name) { name_ = name; } void Set(int age) { age_ = age; } std::string name_; int age_; };
上記のコードを、
下記のコードのようにbindingすると、
同じSetという名前で関数をbindingすることができます。
#include "Pet.h" #include <pybind11/pybind11.h> namespace py = pybind11; PYBIND11_PLUGIN(cpplib) { py::module m("cpplib", "pybind11 example plugin"); py::class_<Pet>(m, "Pet") .def(py::init<const std::string &, int>()) .def("Set", (void (Pet::*)(int)) & Pet::Set) .def("Set", (void (Pet::*)(const std::string &)) & Pet::Set) .def_readwrite("name", &Pet::name_) .def_readwrite("age", &Pet::age_); return m.ptr(); }
上記のオーバーロード関数は
Python側から下記のように利用することができます。
#! /usr/bin/python # -*- coding: utf-8 -*- import cpplib pet = cpplib.Pet("pochi", 10) print(pet.name) print(pet.age) pet.Set("shiro") pet.Set(20) print(pet.name) print(pet.age)
上記のように、関数オーバーロードが簡単にできるため、
関数オーバーロードを使いたい場合は、
普通にPythonをそのまま使うよりも便利かもしれません。
Enum
C++のEnumのような特別な型も
pybind11を使うことで、
pythonコードにbindすることができます。
下記のようなEnumを含むクラスがあった場合、
#include <string> class Pet { public: enum Kind { Dog = 0, Cat }; Pet(const std::string &name, Kind type) : name_(name), type_(type) {} std::string name_; Kind type_; };
下記のコードでbindingできます。
class_のオブジェクトを作成し、
関数の登録と共に、
enum_ APIを元にクラスオブジェクトに登録します。
/** * @brief sample code of pybind11 */ #include "Pet.h" #include <pybind11/pybind11.h> namespace py = pybind11; PYBIND11_PLUGIN(cpplib) { py::module m("cpplib", "pybind11 example plugin"); py::class_<Pet> pet(m, "Pet"); pet.def(py::init<const std::string &, Pet::Kind>()) .def_readwrite("name", &Pet::name_) .def_readwrite("type", &Pet::type_); py::enum_<Pet::Kind>(pet, "Kind") .value("Dog", Pet::Kind::Dog) .value("Cat", Pet::Kind::Cat) .export_values(); return m.ptr(); }
すると、下記のようにPythonからC++のEnumを使うことができます。
intでキャストすると、C++のEnumの数値も取得できます。
#! /usr/bin/python # -*- coding: utf-8 -*- import cpplib pet = cpplib.Pet("pochi", cpplib.Pet.Dog) print(pet.type) pet2 = cpplib.Pet("pochi", cpplib.Pet.Cat) print(pet2.type) print(int(pet2.type))
STLのコンテナのやり取り
C++の関数に入力や出力にSTLのコンテナを使うことは多いですが、
pybind11を使うことで、C++のSTLのコンテナの変数を
pythonのリストやmapに変換し、
データのやり取りをすることができます。
例えば、下記のようにC++のvectorを入力にする関数があるとします。
ちなみにpybind11の注意点として、
bindingする関数は、コンテナデータをポインタや参照で与えられても、
そのデータを更新することが出来ないので、
返り値として、コンテナの変数を返す必要があります。
#include <vector> std::vector<int> append_1(std::vector<int> &v) { v.push_back(1); return v; }
上記のコードをbindingする関数は下記のようになります。
stlのデータをpythonとやり取りする場合は、
pybind11/stl.hをインクルードする必要があります。
このファイルをインクルードすることで、
stlのデータをpythonのlistやmapに自動変換してくれます。
#include "stllib.h" #include <pybind11/pybind11.h> #include <pybind11/stl.h> namespace py = pybind11; PYBIND11_PLUGIN(cpplib) { py::module m("cpplib", "pybind11 example plugin"); m.def("append_1", &append_1); return m.ptr(); }
あとは、
下記のようにPythonのlistを渡すと、
その結果を処理し、結果のvectorを返す関数から、
再びpythonのlistを取得できます。
#! /usr/bin/python # -*- coding: utf-8 -*- import cpplib li = [] li.append(1) li.append(2) print(li) li = cpplib.append_1(li) print(li)
下記が、上記のコードの処理結果です。
バインディングする関数から、
かならずデータのコピーを実施する必要がありますが、
これでSTLを使った関数を処理できます。
pybind11がサポートしているコンパイラ
現時点でpybind11は、下記のコンパイラに対応しています。
Clang/LLVM (any non-ancient version with C++11 support)
GCC (any non-ancient version with C++11 support)
Microsoft Visual Studio 2015 or newer
Intel C++ compiler 16 or newer (15 with a workaround)
Cygwin/GCC (tested on 2.5.1)
サンプルコード
上記のサンプルコードは
すべて下記のGithubリポジトリで公開されています。
より詳しいPythonとCコードの連携方法を学びたい人は
下記の書籍がおすすめです。
- 作者: Kurt W. Smith,中田秀基,長尾高弘
- 出版社/メーカー: オライリージャパン
- 発売日: 2015/06/19
- メディア: 大型本
- この商品を含むブログ (3件) を見る
参考資料
pybind11でC++の関数をpythonから使う - Qiita
MyEnigma Supporters
もしこの記事が参考になり、
ブログをサポートしたいと思われた方は、
こちらからよろしくお願いします。