C++のクラスをPythonで使う

# はじめに
[pybind11](https://github.com/pybind/pybind11)を使ってC++で書いたクラスをPythonで使えるようにしていきます。
また、今回の環境はmacOS High Sierraです。

# pybind11のインストール
[https://github.com/pybind/pybind11](https://github.com/pybind/pybind11)をcloneします。

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

cloneが終わったら、ビルドする前に[pytest](https://docs.pytest.org/en/latest/)をインストールします。（pytestがないとビルドの最後にエラーが出ました。）

```console
$ pip install pytest
```

[pytest](https://docs.pytest.org/en/latest/)のインストールが終わったら以下のコマンドでビルドします。

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


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

```c++:person.h
#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
```

```c++:person.cpp
#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));
}
```

<br>
Personクラスを実装したらpybind11でラップします。

```c++:person_wrap.cpp
#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: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`はそれぞれ書き換えて下さい。

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

```console
$ cmake .
$ make
```

<br>
コンパイルが終わって以下のような.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でインポートして動作確認してみます。

```console
$ 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を使ってみました。想像していたより簡単に書けたのでもうちょっと使ってみようと思います。