目次

pybind11を使ってみる

作成日: 2023-08-10 (木)

第6回 pybind11を使ってみる

プロジェクトレイアウト

.
└── pybind11-examples/
    ├── simple/
    │   ├── simple.cpp
    │   └── Makefile
    └── fakegfx/
        ├── fakegfx.cpp
        ├── sample.py
        └── Makefile

simple

simple.cpp

#define _hypot hypot // work-around for Python 3.6 and Mingw-w64 11.0.0
#include <pybind11/pybind11.h>
namespace py = pybind11;

#include <string_view>
#include <format>
#include <iostream>

namespace s {

struct ZeroDivisionError : std::runtime_error {
    using std::runtime_error::runtime_error;
};

void greet(std::string_view who) {
    std::cout << std::format("Hello, {}!\n", who);
}

int add(int a, int b) {
    return a + b;
}

int sub(int a, int b) {
    return a - b;
}

int mul(int a, int b) {
    return a * b;
}

int div(int a, int b) {
    if (b == 0) {
        throw ZeroDivisionError{"division by zero"};
    }
    return a / b;
}

} // ns s

PYBIND11_MODULE(simple, m) {
    m.def("greet", &s::greet, "Greet to user with Hello.");
    m.def("add", &s::add, "Add first and second numbers.");
    m.def("sub", &s::sub, "Subtract second number from first one.");
    m.def("mul", &s::mul, "Multiply first and second numbers.");
    m.def("div", &s::div, "Divide first number by second one.");
    py::register_exception<s::ZeroDivisionError>(m, "ZeroDivisionError", PyExc_ZeroDivisionError);
}

Makefile

MODNAME = simple

PYBIND11_INCLUDES = $(shell pybind11-config --includes)
CXXFLAGS = -Wall -std=c++20 -fPIC ${PYBIND11_INCLUDES}
LDFLAGS = -LC:\Python36\libs
LDLIBS = -lpython36

all: ${MODNAME}.pyd

${MODNAME}.pyd: ${MODNAME}.o
	${CXX} -shared ${CXXFLAGS} -o $@ $< ${LDFLAGS} ${LDLIBS} 
    
${MODNAME}.o: ${MODNAME}.cpp
	${CXX} -c ${CXXFLAGS} -o $@ $<

fakegfx

fakegfx.cpp

#define _hypot hypot  // work-around for Python 3.6 and Mingw-w64 11.0.0
#include <pybind11/pybind11.h>

namespace py = pybind11;

#include <iostream>

namespace g {

class Shape {
public:
    virtual ~Shape() = default;
    virtual void draw() const = 0;
};

class RectangleShape : public Shape {
public:
    void draw() const override {
        std::cout << "+---------+\n"
                  << "|         |\n"
                  << "|         |\n"
                  << "+---------+\n";
    }
};

void draw_shape(Shape const &shape) {
    shape.draw();
}

// trampolines that redirects virtual call back to Python
class PyShape : public Shape {
public:
    using Shape::Shape;

    void draw() const override {
        PYBIND11_OVERRIDE_PURE(
            void,  // return type
            Shape, // parent type
            draw,  // method name
        );
    }
};

class PyRectangleShape : public RectangleShape {
public:
    using RectangleShape::RectangleShape;
    void draw() const override {
        PYBIND11_OVERRIDE(void, RectangleShape, draw);
    }
};

} // namespace g

PYBIND11_MODULE(fakegfx, m) {
    py::class_<g::Shape, g::PyShape>(m, "Shape")
        .def(py::init())
        .def("draw", &g::Shape::draw);

    py::class_<g::RectangleShape, g::Shape, g::PyRectangleShape>(m, "RectangleShape")
        .def(py::init())
        .def("draw", &g::RectangleShape::draw);
        
    m.def("draw_shape", &g::draw_shape);
}

sample.py

import fakegfx

class CircleShape(fakegfx.Shape):
    def draw(self):
        print("o")

class XRectangleShape(fakegfx.RectangleShape):
    def draw(self):
        print("x x x x x x")
        print("x         x")
        print("x         x")
        print("x x x x x x")

Makefile

MODNAME = fakegfx

PYBIND11_INCLUDES = $(shell pybind11-config --includes)
CXXFLAGS = -Wall -std=c++20 -fPIC ${PYBIND11_INCLUDES}
LDFLAGS = -LC:\Python36\libs
LDLIBS = -lpython36

all: ${MODNAME}.pyd

${MODNAME}.pyd: ${MODNAME}.o
	${CXX} -shared ${CXXFLAGS} -o $@ $< ${LDFLAGS} ${LDLIBS} 
    
${MODNAME}.o: ${MODNAME}.cpp
	${CXX} -c ${CXXFLAGS} -o $@ $<