本稿ではC++コードを含むPythonパッケージの作成方法を紹介します。処理が重い部分をC++でコーディングすることで、パッケージが提供する関数の実行速度を向上させることができます。 pybind11を用いれば意外と(?)簡単に作成することができて素晴らしい。
完成形のイメージ
fibonacciというフィボナッチ数列値を求める関数をもつsequenceパッケージを作成します。
>>> import sequence >>> sequence.fibonacci(10) 55
作成したコードはここ。
手順のアウトライン
"手順(C++コードのみからなるパッケージを作成)" だけでも良いのですが、後からPythonによるカスタマイズが行えるように、"手順(C++パッケージをPythonでラップしたパッケージを作成)" も本稿では紹介します。 やることが多そうですが、pybind11が公式で配布しているものを使うことで、実はあまりコストはありません。
手順 (C++コードのみからなるパッケージを作成)
フィボナッチ数列を求める関数fをC++で書く
フィボナッチ数列の関数fibonacci
を実装します。
fibonacci.hpp
int fibonacci(int n) { if ( n <= 1 ) { return n; } else { return fibonacci(n-1) + fibonacci(n-2); } }
モジュール化する関数やクラスを指定
init.cpp
#include <pybind11/pybind11.h> #include "fibonacci.hpp" PYBIND11_MODULE(sequence_cpp, m) { m.def("fibonacci", &fibonacci); }
ここで、fibonacciという関数を持つsequence_cppという名前のモジュールを作るぞ、と宣言しています。 m.def("関数名", 関数のポインタ)が基本的な文法です。そのほかにもC++クラスをPythonから生成したり、ということも行えます。公式ドキュメト
これらのファイルは次のようなディレクトリ構成で管理することにします。 また、pybind11 のソースコードをこのディレクトリにダウンロードしておきます。 (git clone https://github.com/pybind/pybind11.git もしくは git submodule add https://github.com/pybind/pybind11.git など )
. ├── sequence │ ├── fibonacci.hpp │ └── init.cpp └── pybind11
CMakeLists.txtの作成
cmakeを用いてc++プログラムをコンパイルします。CMakeLists.txtはコンパイル指示書のMakefileを作成する指示書です。pybind11公式がテンプレートを配布しているのでありがてぇ...と言いながら使います。
今回のファイル構成に合わせて書き換え。作業後のファイルは次のリンクを参照。 https://github.com/nariaki3551/pybind11-sample/blob/main/CMakeLists.txt
オリジナルのものとの差分は下記。
$ colordiff ./cmake_example/CMakeLists.txt CMakeLists.txt 2c2 < project(cmake_example) --- > project(sequence_cpp) 4,5c4,7 < pybind11_add_module(cmake_example src/main.cpp) --- > pybind11_add_module(sequence_cpp sequence/init.cpp) > > target_include_directories(sequence_cpp PRIVATE ./sequence) 9c11 < target_compile_definitions(cmake_example --- > target_compile_definitions(sequence_cpp
pybind11_add_moduleにPYBIND11_MODULEを指定したcppファイルを渡します。(複数ある場合は複数指定すれば良いのかな?)
CMakeLists.txtはルートディレクトリに配置
. ├── CMakeLists.txt <--- 追加 ├── sequence │ ├── fibonacci.hpp │ └── init.cpp └── pybind11
コンパイルのテスト
CMakeLists.txtと同じ階層にいる状態で
mkdir build cd build cmake .. make
が問題なく実行できればok.
setup.pyの作成
setup.pyを用いてパッケージ化しましょう。これも上記のテンプレートのものをほぼそのまま使用できます。作業のコードはこのリンクにあります。 https://github.com/nariaki3551/pybind11-sample/blob/main/setup.py
差分は下記です。
$ colordiff ./cmake_example/setup.py setup.py 1d0 < import os 130c129 < name="cmake_example", --- > name="sequence", 132,133c131,132 < author="Dean Moldovan", < author_email="dean0x7d@gmail.com", --- > author="Nariaki Tateiwa", > author_email="nariaki3551@gmail.com", 136c135 < ext_modules=[CMakeExtension("cmake_example")], --- > ext_modules=[CMakeExtension("sequence.sequence_cpp")],
ここで
ext_modules=[CMakeExtension("sequence.sequence_cpp")]
でc++ファイルからなるパッケージをsequenceパッケージ内にsequence_cppという名前で作成することを宣言しています。
setup.pyはルートディレクトリに配置
. ├── CMakeLists.txt ├── sequence │ ├── fibonacci.hpp │ └── init.cpp ├── setup.py <--- 追加 └── pybind11
パッケージ化
python -m pip install .
でパッケージが作成され、利用可能になります。
>>> import sequence.sequence_cpp >>> sequence.sequence_cpp.fibonacci(10) 55
(現在作成中のルートディレクトリで実行するとimportの読み込みが上手くいかないので注意(適当にtmpディレクトリとかを作ってそこで実行すると良いです))
手順 (C++パッケージをPythonでラップしたパッケージを作成)
さて、上記で作成したsequence.sequence_cppをラップしたパッケージを作成する方法を紹介します。
fibonacci.py
import sequence.sequence_cpp def fibonacci(n: int) -> int: print("python::", end="") return sequence.sequence_cpp.fibonacci(n)
ここではsequence.sequence_cpp.fibonaccをラップして、標準出力に"python::"を追加してみました。
__init__.py
from sequence.fibonacci import fibonacci
これらをsequenceディレクトリに追加します。
. ├── CMakeLists.txt ├── sequence │ ├── fibonacci.hpp │ ├── __init__.py <--- 追加 │ ├── fibonacci.py <--- 追加 │ └── init.cpp ├── setup.py └── pybind11
setup.pyの更新
setup.pyを用いてパッケージ化しましょう。差分はsetuptools.setupにpackagesを指定を追加したところですね。これで__init__.pyが存在するディレクトリをベースにパッケージが作成されます。
6c6 < from setuptools import Extension, setup, find_packages --- > from setuptools import Extension, setup 140d139 < packages=find_packages(),
パッケージ化
python -m pip install .
でパッケージが作成 & 利用可能になります。出力は
で、実際に実行してみると
>>> import sequence >>> sequence.fibonacci(10) python::55
でうまく動いていますねぇ。