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.hpybind11をインクルードしサブモジュールのバインド処理を定義します。

#pragma once
#include <pybind11/pybind11.h>

void bind_module1(pybind11::module &m);
void bind_module2(pybind11::module &m);

bind.ccmymoduleという名前の自作モジュールをPYBIND11_MODULEでバインドします。バインド処理はサブモジュールのバインド処理であるbind_module1およびbind_module2を呼び出します。

#include "bind.h"

PYBIND11_MODULE(mymodule, m) {
  bind_module1(m);
  bind_module2(m);
}

サブモジュールのバインド処理の実装

サブモジュールのバインド処理では、m.def_submoduleでサブモジュールを作成します。 作成したサブモジュールに対して、クラスや関数を追加します。 例えば、module1.ccm.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モジュールを作成する際に、サブモジュールごとにファイルを分割する方法を紹介しました。


関連記事






最近の記事