はじめに

pybind11を使ってC++で書いたクラスをPythonで使えるようにしていきます。 また、今回の環境はmacOS High Sierraです。

pybind11のインストール

https://github.com/pybind/pybind11をcloneします。

$ git clone https://github.com/pybind/pybind11

cloneが終わったら、ビルドする前にpytestをインストールします。(pytestがないとビルドの最後にエラーが出ました。)

$ pip install pytest

pytestのインストールが終わったら以下のコマンドでビルドします。

$ cd pybind11
$ mkdir build
$ cd build
$ cmake ..
$ make check -j 2

プログラム

今回は名前、身長、体重をメンバ変数に持つPersonクラスを実装していきます。

#ifndef PERSON_H
#define PERSON_H

#include <string>

class Person
{
public:
    Person();
    Person(const std::string &name, const int &height, const int &weight);

    void SetName(const std::string &name);
    std::string GetName() const;

    void SetHeight(const int &height);
    int GetHeight() const;

    void SetWeight(const int &weight);
    int GetWeight() const;

    double GetBmi() const;
private:
    std::string mName;
    int mHeight;
    int mWeight;
};

#endif
#include "person.h"

Person::Person() :
        mName(""),
        mHeight(0),
        mWeight(0)
{}

Person::Person(const std::string &name, const int &height, const int &weight) :
        mName(name),
        mHeight(height),
        mWeight(weight)
{}

void Person::SetName(const std::string &name)
{
    mName = name;
}

std::string Person::GetName() const
{
    return mName;
}

void Person::SetHeight(const int &height)
{
    mHeight = height;
}

int Person::GetHeight() const
{
    return mHeight;
}

void Person::SetWeight(const int &weight)
{
    mWeight = weight;
}

int Person::GetWeight() const
{
    return mWeight;
}

double Person::GetBmi() const
{
    return mWeight / ((mHeight/100.0) * (mHeight/100.0));
}

Personクラスを実装したらpybind11でラップします。
#include "person.h"
#include <pybind11/pybind11.h>

namespace py = pybind11;

PYBIND11_MODULE(person, p)
{
    py::class_<Person>(p, "Person")
            .def(py::init<std::string, int, int>())
            .def_property("name", &Person::GetName, &Person::SetName)
            .def_property("height", &Person::GetHeight, &Person::SetHeight)
            .def_property("weight", &Person::GetWeight, &Person::SetWeight)
            .def("get_bmi", &Person::GetBmi)
            .def("__repr__", [](const Person &p) {
                return "Person('" + p.GetName() + "', " +
                       std::to_string(p.GetHeight()) + ", " +
                       std::to_string(p.GetWeight()) + ")";
            });
}

コンパイル

今回はcmake使ってみました。 以下のようなCMakeLists.txtを作成しました。

cmake_minimum_required(VERSION 3.9)
project(person)
set(PYBIND11_CPP_STANDARD -std=c++11)
set(CMAKE_CXX_FLAGS "-Wall -O3")
set(CPLUS_INCLUDE_PATH "/usr/local/Cellar/python/3.6.4_3/Frameworks/Python.framework/Versions/3.6/include/python3.6m/")
find_package(pybind11 REQUIRED)

pybind11_add_module(person SHARED person.cpp person_wrap.cpp)

CPLUS_INCLUDE_PATHはそれぞれ書き換えて下さい。


CMakeLists.txtを作成したら以下のコマンドでコンパイルしていきます。
$ cmake .
$ make

コンパイルが終わって以下のような.soファイルができていれば問題ないです。
$ ls
CMakeCache.txt               Makefile                     person.cpython-36m-darwin.so
CMakeFiles                   cmake_install.cmake          person.h
CMakeLists.txt               person.cpp                   person_wrap.cpp

Pythonで使う

実際にPythonでインポートして動作確認してみます。

$ python
Python 3.6.4 (default, Mar  4 2018, 16:34:12)
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from person import Person
>>> p = Person('tarou', 160, 40)
>>> p
Person('tarou', 160, 40)
>>> p.name = 'Tarou'
>>> p.weight = 80
>>> p
Person('Tarou', 160, 80)
>>> p.get_bmi()
31.249999999999993

最後に

最初Boostを使ってスクリプトバインディングをやってみようとしたのですが、なかなか上手くいかなかったので今回はpybind11を使ってみました。想像していたより簡単に書けたのでもうちょっと使ってみようと思います。