10
5

More than 1 year has passed since last update.

pybind11に入門してみた話

Last updated at Posted at 2022-12-10

はじめに

こちらの記事は、eeic (東京大学工学部電気電子・電子情報工学科) Advent Calendar 2022 の 11 日目の記事として書かれたものです。

pybind11 という Python のライブラリを初めて使ってみたので、その紹介を簡単にしようと思います。

pybind11 とは

Python に C++ の関数やクラスを組み込めるライブラリです。
Python3.6+ と C++ の標準ライブラリのみに依存しているため、余計な依存関係をないのがポイントです。
古いコンパイラを無視して C++11 で書かれたものに限定することによって、軽量なヘッダのみのライブラリとすることができたらしいです。
詳しくは 公式ドキュメント をご覧ください。チュートリアルも充実しています。

自作ライブラリの作成

pybind11_demo というライブラリを作成してみました。
ソースコードは こちらの GitHub レポジトリ にあります。

ヘッダファイルを include ディレクトリにまとめています。
src ディレクトリに C++ の実装をまとめています。
integerstructure という2つのサブモジュールがあり、それぞれのサブモジュールの中に関数やクラスが実装されている、という形になっています。
ディレクトリ構造について、 Python の自作ライブラリを書く時みたいに、 __init__.py にあたるものが、各(サブ)モジュール内の (サブ)モジュール名.cpp になっています。

setup.pypyproject.toml はライブラリ作成時に必要なものです。(詳細は省略します。)

pybind11_demo/
    ├── include/
    │       ├── integer.h
    │       ├── pybind11_demo.h
    │       └── structure.h
    ├── src/
    │       ├── integer/
    │       │       ├── add_i16.cpp
    │       │       ├── add_i32.cpp
    │       │       ├── add_i64.cpp
    │       │       └── integer.cpp
    │       ├── structure/
    │       │       ├── pet_private.cpp
    │       │       ├── pet_public.cpp
    │       │       ├── pet.cpp
    │       │       └── structure.cpp
    │       └── pybind11_demo.cpp
    ├── pyproject.toml
    └── setup.py

※VSCode 上での開発に役立ちそうな話

VSCode 上で C/C++ の拡張機能を使いながら c++ のコードを書いていると、

#include <pybind11/pybind11.h>

のところに波線が引かれてしまうと思います。これは、VSCode 上からだと pybind11.h がインクルードのパスの中に見つからないせいなので、VSCode の設定に書き加えてあげます。具体的には、settings.json に以下の内容を追加してあげます。

{
    ...
    // add this block
    "C_Cpp.default.includePath": [
        "hoge",
        "fuga",
    ],
    ...
}

hogefuga の部分にあたる、追加するべきインクルードパスは、以下のコマンドで調べることができます。

python -m pybind11 --includes

挙動の確認

整数どうしの足し算

以下のように、整数どうしの足し算であっても、使用する型によって異なる関数を定義することができます。今回は、16 / 32 / 64 bit の符号付き整数を試してみます。

// 16bit 符号付き整数どうしの足し算
short add_i16(short i, short j) {
    return i + j;
}

// 32bit 符号付き整数どうしの足し算
int add_i32(int i, int j) {
    return i + j;
}

// 64bit 符号付き整数どうしの足し算
long long add_i64(long long i, long long j) {
    return i + j;
}

これらの関数を Python から呼び出して使ってみましょう。

>>> add_i16(1, 2)
3
>>> add_i32(1, 2)
3
>>> add_i64(1, 2)
3

どれも正しい結果が返ってきています。
Python といえば(?)多倍長整数なので、その挙動も見てみましょう。

>>> add_i16(2**14, 2**14)
-32768
>>> add_i32(2**14, 2**14)
32768
>>> add_i64(2**14, 2**14)
32768

short 型は $[-2^{15}, 2^{15})$ の値しか取れないのですが、 $2^{14} + 2^{14} = 2^{15}$($010...0_{(2)} + 010...0_{(2)} = 100...0_{(2)}$)となってしまい、オーバーフローしてしまいます。(詳細が分からない方は 2の補数 とかで調べてみてください。)
さらに大きな整数で計算を試してみましょう。

>>> add_i16(2**30, 2**30)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: add_i16(): incompatible function arguments. The following argument types are supported:
    1. (arg0: int, arg1: int) -> int
Invoked with: 1073741824, 1073741824
>>> add_i32(2**30, 2**30)
-2147483648
>>> add_i64(2**30, 2**30)
2147483648

short 型ではエラーが出るようになりました。エラーの内容では、「非互換な関数の引数($1073741824 = 2^{30}, 1073741824 = 2^{30}$)がある」と言われています。short 型の取りうる値以外の入力があると、エラーが出るみたいですね。
int 型では、先ほどの short 型と同じような原因で、間違った計算結果になってしまっています。

自作クラス

基本となる Pet クラスに基づいて、以下のような少しずつ異なるクラスを定義します。(詳しくは ソースコード をご覧ください。)

  • 通常のクラス(Pet
  • Python 接続時に name メンバ変数の読み書き権限を与えたクラス(PetPublic
  • c++ のクラス内で naem を private なメンバ変数としたクラス(PetPrivate
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; }
        std::string name;
};

各クラスに __repr__ を実装してあるので、それぞれ見ていきましょう。

>>> pet = Pet("hoge")
>>> pet_public = PetPublic("hoge")
>>> pet_private = PetPrivate("hoge")
>>> print(pet)
<pybind11_demo.structure.Pet named 'hoge'>
>>> print(pet_public)
<pybind11_demo.structure.PetPublic named 'hoge'>
>>> print(pet_private)
<pybind11_demo.structure.PetPrivate>

__repr__ を実装する際に、name メンバ変数にアクセスすることでペットの名前も表示することを実現したかったのですが、PetPrivate クラスでは name メンバ変数が private なので、直接アクセスする形では使用することができません。
次に、Python 側から name メンバ変数に他の名前を直接代入できるか見てみましょう。

>>> pet.name = "fuga"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'pybind11_demo.structure.Pet' object has no attribute 'name'
>>> pet_public.name = "fuga"
>>> pet_public.name
'fuga'
>>> pet_private.name = "fuga"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

Python 上での Pet クラスには name メンバ変数はないよと怒られてしまっています。以下のように、メンバ変数もちゃんと定義してあげないと、public なメンバ変数であっても、アクセスすることはできません。

.def_readwrite("name", &PetPublic::name)

また、PetPrivate クラスにおいて name は、getName メンバ関数を利用して、以下のように read-only な property として定義されています。そのため、can't set attribute というエラーが出されています。(メンバ変数とプロパティを並列に紹介してしまっていいのかは微妙ですが、機能の紹介程度に思っていただければです。)

.def_property_readonly("name", &PetPrivate::getName)

おわりに

pybind11 ライブラリに入門してみた話でした。C++ で実装した関数や構造体を Python で手軽に使いたいと思った時、こちらのライブラリを思い出してみてください。

余談

chatGPT 呼ばれる大規模言語モデルが公開されました。これに「pybind11に入門してみた話」というクエリを投げてみたところ、以下の出力が得られました。

pybind11は、C++とPythonを比較的簡単に統合するためのライブラリです。これにより、PythonプログラマーがC++コードを使用して高速なプログラミングを行えるようになります。また、C++プログラマーがPythonの拡張モジュールを作成するのにも便利です。

pybind11を使用するためには、まずpybind11をインストールする必要があります。これは通常、pipを使用して行うことができます。

pip install pybind11

インストールが完了したら、次に、C++のコードをPythonの拡張モジュールに変換するために必要なpybind11のヘッダーファイルをインクルードします。

#include <pybind11/pybind11.h>

次に、Pythonの拡張モジュールを作成するためのpybind11の関数を呼び出します。この関数には、拡張モジュールの名前とC++の関数を引数として渡します。

PYBIND11_MODULE(my_extension, m) {
    m.def("my_function", &my_function);
}

これで、Pythonプログラマーは拡張モジュールをインポートし、C++の関数を呼び出すことができます。

import my_extension

my_extension.my_function()

pybind11を使用することで、C++とPythonを統合することができます。これにより、Pythonプログラマーが高速なC++コードを使用したプログラミングを行うことができます。また、C++プログラマーはPythonの拡張モジュールを簡単に作成することができます。

やばいですね☆
簡単な記事程度なら、このAI使えば書けてしまいそうです......

※参考スクショ
sukusho1.png
sukusho2.png

参考記事

  1. pybind11 documentation
  2. Pybind11をパッケージ化するまでの流れ
  3. 【pybind11】サブモジュールごとにファイルを分割してPythonモジュールを作成する
10
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5