Python
C++
Python3
pybind11

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


はじめに

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クラスを実装していきます。


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



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));
}





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


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を作成しました。


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