pybind11 で Python から C++ を呼び出す
[History] [Last Modified] (2019/09/23 15:48:49)
ここは
趣味のプログラミングを楽しむための情報共有サービス。記事の一部は有料設定にして公開できます。 詳しくはこちらをクリック📝
Recent posts
Popular pages

概要

C++ を Python から利用する方法の一つに pybind11 があります。C++11 をサポートするコンパイラが必要です。サンプルコードを記載します。

簡単なサンプル

C++11 コードをビルドして、Python から利用するための .so ファイルを生成します。ビルド方法は複数ありますがここでは CMake を利用します。

Building with CMake、参考: git submodule

$ git init
$ git submodule add https://github.com/pybind/pybind11.git
$ mkdir build
$ ls
CMakeLists.txt  build  example.cpp  main.py  pybind11

$ cat CMakeLists.txt
cmake_minimum_required(VERSION 2.8.12)
project(example)

add_subdirectory(pybind11)
pybind11_add_module(example example.cpp)

$ cat example.cpp
#include <pybind11/pybind11.h>

int add(int i, int j) {
    return i + j;
}

PYBIND11_MODULE(example, m) {
    m.doc() = "pybind11 example plugin"; // optional module docstring
    m.def("add", &add, "A function which adds two numbers");
}

$ cat main.py
import example
print example.add(1, 1)

example.so のビルド

cd build
cmake -DPYTHON_EXECUTABLE=`which python` ..
make

実行例

$ PYTHONPATH=$PYTHONPATH:build python ../main.py
2

Python が複数インストールされている場合の注意点

インストールされている Python のバージョンは CMake が自動的に検出します。検出された情報は CMakeCache.txt 内に記載されます。

$ grep python CMakeCache.txt  | head -1
PYTHON_EXECUTABLE:FILEPATH=/path/to/python3.6

複数バージョンの Python が存在する環境においては、利用する Python 実行ファイルへのパスを指定します

cmake -DPYTHON_EXECUTABLE=/usr/bin/python2 ..

以下のようにバージョンを指定する方法もあります

cmake -DPYBIND11_PYTHON_VERSION=2.7 ..

CMakeLists.txt 内で指定することもできます。

set(PYBIND11_PYTHON_VERSION 2.7)

または

set(PYTHON_EXECUTABLE /usr/bin/python2)

C++ バージョンの指定

add_subdirectory より前に指定します。

set(PYBIND11_CPP_STANDARD -std=c++11)

または

set(PYBIND11_CPP_STANDARD -std=c++14)

関数のバインディング

int add(int i, int j) {
    return i + j;
}

コメントなし、キーワード引数なし、デフォルト引数なし

m.def("add", &add);

キーワード引数

参考: 名前空間 (C++をもう一度)

namespace py = pybind11;
m.def("add", &add, py::arg("i"), py::arg("j"));

または

namespace py = pybind11;
using namespace py::literals;
m.def("add", &add, "i"_a, "j"_a);

Python

print example.add(i=1, j=1)

デフォルト引数

namespace py = pybind11;
m.def("add", &add, py::arg("i") = 1, py::arg("j") = 1);

または

namespace py = pybind11;
using namespace py::literals;
m.def("add", &add, "i"_a=1, "j"_a=1);

Python

print example.add()

値のバインディング

m.attr("xxx") = 123;

namespace py = pybind11;
py::object yyy = py::cast("mystr");
m.attr("yyy") = yyy;

Python

print example.xxx
print example.yyy

クラスのバインディング

コンストラクタ、メンバ関数

example.cpp

#include <pybind11/pybind11.h>

struct Pet {
    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;
};

namespace py = pybind11;

PYBIND11_MODULE(example, m) {
    py::class_<Pet>(m, "Pet") // 構造体であっても class_ でバインディングします。
        .def(py::init<const std::string &>()) // コンストラクタの引数です。
        .def("setName", &Pet::setName)
        .def("getName", &Pet::getName);
}

main.py

import example
p = example.Pet('xxx')
print p
print p.getName()
p.setName('yyy')
print p.getName()

補足

静的メンバ関数

静的メンバ関数をバインディングするためには def ではなく def_static を利用します。

example.cpp

#include <pybind11/pybind11.h>
#include <iostream>

class MyClass {
public:
    static void Show() { std::cout << m_mystr << std::endl; }
private:
    static std::string m_mystr;
};

std::string MyClass::m_mystr = "hello";

PYBIND11_MODULE(example, m) {
    pybind11::class_<MyClass>(m, "MyClass")
        .def_static("Show", &MyClass::Show);
}

main.py

import example
example.MyClass.Show() #=> hello

メンバ変数、静的メンバ変数

メンバ変数

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

メンバ定数

.def_readonly("name", &Pet::name)

private メンバ変数

.def_property("name", &Pet::getName, &Pet::setName)

private メンバ定数

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

静的メンバ変数および静的メンバ定数のバインディングには以下を利用します。

def_readwrite_static()
def_readonly_static()
def_property_static()
def_property_readonly_static()

継承

#include <pybind11/pybind11.h>

struct MyClass {
    MyClass(const std::string &name) : name(name) { }
    const std::string getName() const { return name; }
    std::string name;
};

struct MySubClass : MyClass {
    MySubClass(const std::string &name) : MyClass(name) { }
};

PYBIND11_MODULE(example, m) {

    pybind11::class_<MyClass>(m, "MyClass")
        .def(pybind11::init<const std::string &>())
        .def("getName", &MyClass::getName)
        .def_readwrite("name", &MyClass::name);

    pybind11::class_<MySubClass, MyClass>(m, "MySubClass")
        .def(pybind11::init<const std::string &>());

    // あるいは以下のようにもできます
    // myClass.def(pybind11::init<const std::string &>())
    //     .def("getName", &MyClass::getName)
    //     .def_readwrite("name", &MyClass::name);

    // pybind11::class_<MySubClass>(m, "MySubClass", myClass)
    //     .def(pybind11::init<const std::string &>());
}

Python

import example
sub = example.MySubClass('myname')
print sub.name #=> myname
print sub.getName() #=> myname

ラムダ式によるバインディング

C++ 側で実装されていない関数をラムダ式で定義してバインディングしたり、C++ における実装をラムダ式で拡張して同名でバインディングしたりできます。Python の __repr__ を実装する場合にも使うことができます。

.def("__repr__",
     [](const Pet &a) {
         return "<example.Pet named '" + a.name + "'>";
     }
);

関数オーバーロード

関数ポインタにキャストすることでオーバーロードされた関数を pybind できます。

#include <pybind11/pybind11.h>
#include <iostream>

void f(int i) {
    std::cout << "f: " << i << std::endl;
}

void f(const std::string &s) {
    std::cout << "f2: " << s << std::endl;
}

PYBIND11_MODULE(example, m) {

    m.def("f", (void (*)(int)) &f)
        .def("f", (void (*)(const std::string &)) &f);

}

Python

import example
example.f(123) #=> f: 123
example.f('aaa') #=> f2: aaa

C++14 に対応したコンパイラの場合は pybind11::overload_cast が利用できます。

m.def("f", pybind11::overload_cast<int>(&f))
    .def("f", pybind11::overload_cast<const std::string &>(&f));

列挙体

単純な列挙体は以下のように pybind します。

#include <pybind11/pybind11.h>

enum MyEnum {
    VALUE_A,
    VALUE_B,
    VALUE_C
};

PYBIND11_MODULE(example, m) {

    pybind11::enum_<MyEnum>(m, "MyEnum")
        .value("VALUE_A", MyEnum::VALUE_A)
        .value("VALUE_B", MyEnum::VALUE_B)
        .value("VALUE_C", MyEnum::VALUE_C);
}

Python

import example
b = example.MyEnum.VALUE_B
int(b) #=> 1
b.name #=> u'VALUE_B'

In [11]: example.MyEnum.__members__
Out[11]: 
{u'VALUE_A': MyEnum.VALUE_A,
 u'VALUE_B': MyEnum.VALUE_B,
 u'VALUE_C': MyEnum.VALUE_C}

クラス内の列挙体は以下のようにします。

#include <pybind11/pybind11.h>

struct Pet {
    enum Kind {
        Dog = 0,
        Cat
    };

    Pet(const std::string &name, Kind type) : name(name), type(type) { }

    std::string name;
    Kind type;
};

PYBIND11_MODULE(example, m) {

    pybind11::class_<Pet> pet(m, "Pet");

    pet.def(pybind11::init<const std::string &, Pet::Kind>())
        .def_readwrite("name", &Pet::name)
        .def_readwrite("type", &Pet::type);

    pybind11::enum_<Pet::Kind>(pet, "Kind")
        .value("Dog", Pet::Kind::Dog)
        .value("Cat", Pet::Kind::Cat)
        .export_values();
}

Python

from example import Pet
p = Pet('Lucy', Pet.Cat)
print p.type #=> Kind.Cat
print p.type.name #=> u'Cat'
print int(p.type) #=> 1
print Pet.Kind.__members__ #=> {u'Dog': Kind.Dog, u'Cat': Kind.Cat}

C++11 スマートポインタ

unique_ptr

#include <pybind11/pybind11.h>

#include <memory>
#include <iostream>

struct MyClass {
    ~MyClass() {
        std::cout << "myclass destroyed" << std::endl;
    }
};

std::unique_ptr<MyClass> createMyClass() {
    return std::unique_ptr<MyClass>(new MyClass());
}

PYBIND11_MODULE(example, m) {
    pybind11::class_<MyClass>(m, "MyClass")
        .def(pybind11::init<>());

    m.def("createMyClass", &createMyClass);
}

Python

from example import createMyClass

obj = createMyClass()
print 'obj exists'
obj = None
print 'obj is none'

実行例

obj exists
myclass destroyed
obj is none

shared_ptr

#include <pybind11/pybind11.h>

#include <memory>
#include <iostream>

class Shared {
public:
    ~Shared() {
        std::cout << "shared destroyed" << std::endl;
    }
};

class MyClass {
public:
    MyClass() : shared(std::make_shared<Shared>()) { }
    ~MyClass() {
        std::cout << "myclass destroyed" << std::endl;
    }
    std::shared_ptr<Shared> getShared() { return shared; }
private:
    std::shared_ptr<Shared> shared;
};

PYBIND11_MODULE(example, m) {
    pybind11::class_<Shared, std::shared_ptr<Shared>>(m, "Shared");

    pybind11::class_<MyClass, std::shared_ptr<MyClass>>(m, "MyClass")
       .def(pybind11::init<>())
       .def("getShared", &MyClass::getShared);
}

Python

from example import MyClass

shared = MyClass().getShared()
print 'shared exists'
shared = None
print 'shared none'

実行例

myclass destroyed
shared exists
shared destroyed
shared none

その他

Related pages