【pybind11】サブモジュールごとにファイルを分割してPythonモジュールを作成する
pybind11を使うことでC++で書いたライブラリをPythonから利用できるようになります。 例えばDeep LearningフレームワークのPyTorchもコア部分をC++で実装し、pybind11を使ってPythonから利用できるようになっています。 pybind11の公式リファレンスを読むことで基本的なことはわかったのですが、Pythonモジュールをサブモジュールごとにファイルに分割する方法がわからなかったので本記事を作成しました。 特にプログラムが複雑になってきたときにファイルを分割してサブモジュールを作成したいことが出てくると思います。 ベストプラクティスなのかわかりませんが、本記事が同じ疑問を持っている方の解決策になれば幸いです。
目次
サンプルの構成
本記事では以下のような構成とします。プログラムのインストールにはCMakeを使います。
本記事ではバージョン3.16.3
で動作確認しています。
mymodule
├── CMakeLists.txt
├── bind.cc
├── bind.h
├── mymodule1.cc
├── mymodule1.h
├── mymodule2.cc
├── mymodule2.h
└── third_party
pybind11をgit submoduleで追加
最初に準備として、C++ライブラリのトップディレクトリに移動し、gitレポジトリとします。
その後、pybind11
をこのライブラリのsubmoduleとして追加します。
cd mymodule
git init
mkdir third_party
git submodule add https://github.com/pybind/pybind11.git third_party/pybind11
プログラム例
本記事ではmymodule
というPythonモジュールがmymodule1
およびmymodule2
というサブモジュールを含むような場合を考えます。
C++側の構成としては、bind.cc
で各サブモジュールのバインド処理を呼び出すようにして、各サブモジュールの実装であるmymodule1.cc
およびmymodule2.cc
でバインド処理を実装するようにしました。
サブモジュールのバインド処理の呼び出し
bind.h
でpybind11
をインクルードしサブモジュールのバインド処理を定義します。
#pragma once
#include <pybind11/pybind11.h>
void bind_module1(pybind11::module &m);
void bind_module2(pybind11::module &m);
bind.cc
でmymodule
という名前の自作モジュールをPYBIND11_MODULE
でバインドします。バインド処理はサブモジュールのバインド処理であるbind_module1
およびbind_module2
を呼び出します。
#include "bind.h"
PYBIND11_MODULE(mymodule, m) {
bind_module1(m);
bind_module2(m);
}
サブモジュールのバインド処理の実装
サブモジュールのバインド処理では、m.def_submodule
でサブモジュールを作成します。
作成したサブモジュールに対して、クラスや関数を追加します。
例えば、module1.cc
はm.def_submodule("module1")
とすることで、mymodule
のサブモジュールとしてmodule1
を作成します。
module1
に対して、Module1
クラスを定義しています。
mymodule1.h
#pragma once
class Module1 {
};
mymodule1.cc
#include "bind.h"
#include "mymodule1.h"
void bind_module1(pybind11::module &m) {
pybind11::module module1 = m.def_submodule("module1");
pybind11::class_<Module1>(module1, "Module1")
.def(pybind11::init<>());
}
本記事ではモジュールの具体的な中身を実装しないので、mymodule2
はほぼmymodule1
と同じです。
mymodule2.h
#pragma once
class Module2 {
};
mymodule2.cc
#include "bind.h"
#include "mymodule2.h"
void bind_module2(pybind11::module &m) {
pybind11::module module2 = m.def_submodule("module2");
pybind11::class_<Module2>(module2, "Module2")
.def(pybind11::init<>());
}
CMakeLists.txt
以下のようにします。ポイントは、作成した共有ライブラリをPythonで利用できるような形式として、Pythonのsite-packages
以下にインストールしている点です。
project(mymodule)
find_package(PythonLibs REQUIRED)
set(CMAKE_CXX_STANDARD 11)
add_subdirectory(third_party/pybind11)
message("pybind11_FOUND: ${pybind11_FOUND}")
message("PYTHONLIBS_FOUND: ${PYTHONLIBS_FOUND}")
add_library(mymodule_python SHARED bind.h bind.cc mymodule1.h mymodule1.cc mymodule2.h mymodule2.cc)
target_include_directories(mymodule_python PUBLIC ${pybind11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS})
target_link_libraries(mymodule_python ${PYTHON_LIBRARIES})
set_target_properties(mymodule_python PROPERTIES OUTPUT_NAME "mymodule")
pybind11_extension(mymodule_python)
pybind11_strip(mymodule_python)
# Pythonのsite-package以下にインストールするためパスを取得します。
execute_process(
COMMAND "${PYTHON_EXECUTABLE}" -c "if True:
import site
print(site.getsitepackages()[0])"
OUTPUT_VARIABLE Python_SITELIB
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# site-package以下にライブラリをインストールします。
install(TARGETS mymodule_python DESTINATION ${Python_SITELIB})
利用例と実行結果
それでは作成したライブラリを実際に利用してみます。 まずはライブラリをインストールします。
mkdir build
cd build
cmake ..
make
make install
次に以下のPythonプログラムを実行します。
from mymodule.module1 import Module1
from mymodule.module2 import Module2
print(Module1)
print(Module2)
期待した結果を得ることができました。
$python sample.py
<class 'mymodule.module1.Module1'>
<class 'mymodule.module2.Module2'>
おわりに
本記事ではpybind11を使ってPythonモジュールを作成する際に、サブモジュールごとにファイルを分割する方法を紹介しました。