なぜPythonでC++コードを呼び出したいのか...それは高速化のためです.
そもそもPythonの実行速度は非常に遅く,C++と比べて経験的に10倍から100倍程度のパフォーマンスの違いがあります.
この記事では,PythonからC++を呼び出して利用するためのライブラリである「pybind11」の使い方を記載します.
pybind11とは
PythonでC++を利用するためのライブラリです.似たようなライブラリとして,Boost.Pythonなどもありますが,Boostよりも比較的簡単にインストールできるため,利用しやすいものです.簡単にいえば,余計なものを除いた軽量のBoost.Pythonのようです.(引用より)
Think of this library as a tiny self-contained version of Boost.Python with everything stripped away that isn’t relevant for binding generation.
https://github.com/pybind/pybind11
pybind11のインストール
venv環境
まずは,仮想環境として,venvを利用します.仮想環境が不要であれば,この手順はスキップできます.
python -m venv my_venv
source my_venv/bin/activate
pybind11のインストール
pipでpybind11をインストールします.
pip install pybind11
システムにインストールしたい場合は,下記コマンドになります.
pip install pybind11[global]
使い方
公式ドキュメントの例題を利用して,使い方を示します.
PythonでC++を呼び出すための手順は下記のようになります.
- C++で呼び出したい関数やクラスを作成し,バインディングコードを作成
- pybind11を利用し,C++コードをビルド
- Pythonでimportして実行
C++のコードを記述
下記のようなコードを作成します.引数を加算して返り値とする簡単な関数です.
example.cpp
#include <pybind11/pybind11.h>
int add(int i, int j) {
return i + j;
}
PYBIND11_MODULE(example, m) {
m.doc() = "pybind11 example plugin"; // optional module docstring
m.def("add", &add, "A function that adds two numbers");
}
1行目と7-10行目がpybind11のバインディングコードです.
次に下記のように,ビルドします.
g++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix)
ビルドに成功すると,example.cpython-311-x86_64-linux-gnu.so
という名前の共有ライブラリが作成されます.
C++ライブラリが作成できたら,続いてPythonコードを下記のように記述します.
example.py
import example
def main():
print(example.add(1,2))
if __name__ == "__main__":
main()
1行目で作成したライブラリをインポートします.インポートする名前はexample.cpp
の7行目で指定した名前(example)になります.
Pythonを実行すると下記のような結果が得られます.結果は3=1+2です.
python example.py
3
クラスを使用する場合
上記では,C++関数をPythonで使用する方法を示しました.ただ,実用上は関数ではなくクラスを用いることが大半です.
ここからはC++クラスをPythonで呼び出す方法を示します.
下記のように,クラスを含むC++コードを用意します.
my_class.cpp
#include <pybind11/pybind11.h>
#include <string>
class Animal {
public:
virtual ~Animal() {}
virtual std::string go(int n_times) = 0;
};
class Dog : public Animal {
public:
std::string go(int n_times) override {
std::string result;
for (int i = 0; i < n_times; ++i)
result += "woof! ";
return result;
}
};
std::string call_go(Animal *animal) { return animal->go(3); }
PYBIND11_MODULE(my_class, m) {
pybind11::class_<Animal>(m, "Animal").def("go", &Animal::go);
pybind11::class_<Dog, Animal>(m, "Dog").def(pybind11::init<>());
m.def("call_go", &call_go);
}
ビルドは,関数の場合もクラスの場合も全く同じです.
g++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) my_class.cpp -o my_class$(python3-config --extension-suffix)
同様にPythonでimportし,クラスを呼び出します.
import my_class
def main():
animal = my_class.Dog()
print(my_class.call_go(animal))
if __name__ == "__main__":
main()
python my_class.py
woof! woof! woof!
C++のクラスを継承するなど,ある程度複雑なこともできるらしいですが,今回はそこまで扱いません(というかC++で書けばよいと思います).詳しくは下記URL.
https://pybind11.readthedocs.io/en/stable/advanced/classes.html
まとめ
PythonからC++を呼び出すためのライブラリとしてpybind11の使い方を紹介しました.
PythonからC++に変更するだけで十分に高速ですが,並列化などによりさらなる高速化することが可能です.