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クラスを実装していきます。
#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を使ってみました。想像していたより簡単に書けたのでもうちょっと使ってみようと思います。
Thanks for reading!