pybind11

You might also like

Download as pptx, pdf, or txt
Download as pptx, pdf, or txt
You are on page 1of 25

Introduction to pybind11

Brian Jantzen

Unrestricted
Why??? !!!
Pybind11 - Project
• Lightweight Header-Only Library
• Exposes C++ types in Python ( & vice versa )
• Supported Compilers ( C++ 11 and higher ):
– Clang/LLVM, GCC, MSVC, Intel
• Started in 2015, currently version 2.2.+
• Copywrite, “AS-IS”
• https://github.com/pybind/pybind11
• 6.7k Stars
Pybind11 - Project
• Simple to install !!
• Simple to build !!
• Good Documentation : 150 pages
• Open unit tests : pytest
• Headers are readable!
• Support for Python 2.7, 3.x and PyPy 5.7+
• Goals and Syntax are similar to Boost Python
• Well supported and has good activity
Python Extension Choices
• Python C API ( with CPython )
• ctypes
• cffi (pypy)
• SWIG
• Cython
• Boost.Python
• Pybind11
Demo Calling C++ Functions
double cpp_add(double x, double y)
{
return x + y;
}
double cpp_sub(double x, double y)
{
return x – y;
}
Demo Calling C++ Functions
#include <pybind11/pybind11.h>
namespace py = pybind11;
using namespace py::literals;

double cpp_add(double x, double y)


{
return x + y;
}
double cpp_sub(double x, double y)
{
return x – y;
}

PYBIND11_MODULE(demo1, m)
{
m.doc() = “Module to call simple cpp functions”;

m.def(“pyAdd”, &cpp_add, “Add two floating point numbers”,


py::arg(“x”), py::arg(“y”));
m.def(“pySub”, &cpp_sub, “Subtract floating point number from another”,
“x”_a, “y”_a);
}
Steps to build Python Module
(without using cmake)

Build Instruction using MSVC and anaconda 64bit

• call vcvars64.bat
• Compile Command:
cl /nologo /EHsc /Ox /favor:INTEL64 /std:c++17
/Ic:\PYBIND11_PATH\include /Ic:\anaconda3\include
/c demo1.cxx
.. Or ..
set INCLUDE=%INCLUDE%;c:\PYBIND11_PATH\include;c:\anaconda3\include
cl /nologo /EHsc /Ox /favor:INTEL64 /std:c++17
/c demo1.cxx
Steps to build Python Module
• Link Command to produce pyd file
link /nologo /dll /libpath:c:\anaconda3\libs
/out:demo1.pyd demo1.obj

.. OR ..
set LIB=%LIB%;c:\anaconda3\libs
link /nologo /dll /out:demo1.pyd demo1.obj
Python
>>> from demo1 import *
>>> help(pyAdd)
Help on built-in function pyAdd in module demo1:

pyAdd(...) method of builtins.PyCapsule instance


pyAdd(x: float, y: float) -> float

Add two floating point numbers

>>> help(pySub)
Help on built-in function pySub in module demo1:

pySub(...) method of builtins.PyCapsule instance


pySub(x: float, y: float) -> float

Subtract floating point number from another

>>> pyAdd(5,7)
12.0
>>> pySub(5,7)
-2.0
>>> pySub(y=7, x=5)
-2.0
Demo using C++ Class
class Point
{
public:
Point(double x, double y) : m_x(x), m_y(y) {}

double getX(void) const { return m_x; }


double getY(void) const { return m_y; }

void setX(double x) { m_x = x; }


void setY(double y) { m_y = y; }

Point& operator*=(double s) { m_x *= s; m_y *= s; }

Point operator*(double s) const { return Point{m_x * s, m_y * s}; }

std::string toString(void) const {


return "[" + std::to_string(m_x) + ", " + std::to_string(m_y) + "]";
}

// ( I don't want expose getR to python )


double toR(void) const { return sqrt(m_x*m_x + m_y+m_y); }
private:
double m_x, m_y;
};
C++ Binding
#include <pybind11/pybind11.h>
#include <pybind11/operators.h>
#include <string>
#include <cmath>

namespace py = pybind11;
using namespace py::literals;

class Point
{
… CODE HERE …
};

PYBIND11_MODULE(demo2, m)
{
m.doc() = "Module to interact with a simple cpp class";
C++ Binding
py::class_<Point>(m, "Point")
.def(py::init<double, double>())
.def_property("x", &Point::GetX, &Point::SetX)
.def_property("y", &Point::GetY, &Point::SetY)
.def("getX", &Point::GetX)
.def("getY", &Point::GetY)
.def("__str__", &Point::toString)
.def(py::self * double())
.def("__mul__", [](const Point &p, double s)
{ return p * s; }, py::is_operator() )
.def("__repr__", [](const Point &p)
{ return "<Point x=" + std::to_string(p.GetX()) + ", " +
"y=" + std::to_string(p.GetY()) + ">"; });
}
Python
>>> from demo2 import Point
>>> p1 = Point(1.0, 2.0)
>>> p1.x, p1.y
(1.0, 2.0)
>>> p1 *= 5.0
>>> str(p1)
'[5.000000, 10.000000]'
>>> p2 = p1 * 3.
>>> p2
<Point x=15.000000, y=30.000000>
Python lists converted to std containers
… snip …
#include <pybind11/stl.h>

int append( std::vector<int> &v, int i ) {


v.push_back(i);
return v.size();
}

struct IVector {
std::vector<int> m_vector;
};

PYBIND11_MODULE(demo3, m)
{
m.doc() = "Module to demo std container";

m.def("append", &append, "Append to vector", "v"_a, "i"_a );

py::class_<IVector>(m, "IVector")
.def(py::init<>())
.def_readwrite("contents", &IVector::m_vector);
}
Python
• Lists are converted to std::vector, but not returned by reference

from demo3 import append,IVector

def test_func():
list = [ 1, 2, 3, 4 ]
assert append(list, 5) == 5
assert len(list) == 4
assert list == [ 1, 2, 3, 4 ]

def test_class():
c = IVector()
c.contents = [ 1, 2, 3, 4 ]
assert len(c.contents) == 4
c.contents.append(10)
assert len(c.contents) == 4

test_demo3.py ..
Opaque std containers
… snip …
#include <pybind11/stl_bind.h>

PYBIND11_MAKE_OPAQUE(std::vector<int>);

struct IVector {
std::vector<int> m_vector;
};

PYBIND11_MODULE(demo4, m)
{
m.doc() = "Module to demo opaque std container";

py::bind_vector<std::vector<int>>(m, "IList", py::buffer_protocol());

py::class_<IVector>(m, "IVector")
.def(py::init<>())
.def_readwrite("contents", &IVector::m_vector);
}
Python
from demo4 import *

def test_list():
c = IList( [ 1, 2, 3, 4 ] )
assert len(c) == 4
c.append(10)
assert len(c) == 5
assert list(c) == [ 1, 2, 3, 4, 10 ]

def test_class():
c = IVector()
c.contents.extend( [ 1, 2, 3, 4 ] )
assert len(c.contents) == 4
c.contents.append(10)
assert len(c.contents) == 5
assert list(c.contents) == [ 1, 2, 3, 4, 10 ]

test_demo4.py ..
Python buffer view/numpy
class Matrix
{
public:
Matrix(size_t rows, size_t cols) : m_rows(rows), m_cols(cols) {
m_data = new double[m_rows * m_cols];
memset(m_data, 0, sizeof(double)*m_rows*m_cols);
}

<… snip …>

double operator()(size_t row, size_t col) const {


return m_data[row*m_cols + col];
}
double* data() {
return m_data;
}
private:
size_t m_rows;
size_t m_cols;
double *m_data;
};
Matrix Binding
py::class_<Matrix>(m, "Matrix", py::buffer_protocol())
.def(py::init<size_t, size_t>())
.def_buffer( [](Matrix &m) -> py::buffer_info {
return py::buffer_info(
m.data(),
{ m.rows(), m.cols() },
{ sizeof(double) * m.cols(), sizeof(double) } );
})
.def(py::init( [](py::buffer b) {
py::buffer_info info = b.request();
if (info.format != py::format_descriptor<double>::format() ||
info.ndim != 2)
throw std::runtime_error("Incompatible buffer format");
auto m = new Matrix(info.shape[0], info.shape[1]);
memcpy(m->data(), info.ptr,
sizeof(double)*info.shape[0]*info.shape[1]);
return m;
}))
<… snip …>
.def("rows", &Matrix::rows);
Python : __init__ from numpy array
>>> from demo5 import Matrix
>>> import numpy as np
>>> a = np.array( np.arange(1,7)
.astype(np.float64).reshape(2,3)
>>> m = Matrix(a)
>>> m[0,0],m[0,1],m[0,2]
(1.0, 2.0, 3.0)
>>> m[1,0],m[1,1],m[1,2]
(4.0, 5.0, 6.0)
Python : Share buffer view
>>> b = np.array(m, copy=False)
>>> b
array([[ 1., 2., 3.],
[ 4., 5., 6.]])
>>> b[0] = [7.,8.,9.]
>>> b
array([[ 7., 8., 9.],
[ 4., 5., 6.]])
>>> m[0,0],m[0,1],m[0,2]
(7.0, 8.0, 9.0)
>>> m[1,0],m[1,1],m[1,2]
(4.0, 5.0, 6.0)
Undocumented Tip!
def unload_mod(module):
dll = ctypes.CDLL(module + '.pyd')
import _ctypes
_ctypes.FreeLibrary(dll._handle)
_ctypes.FreeLibrary(dll._handle)

def reload_mod(module):
import _ctypes
print(_ctypes.LoadLibrary(module + '.pyd'))
print(_ctypes.LoadLibrary(module + '.pyd'))
More ++!
• Function Pointers
• Smart Pointers
• Exception
• Enumerations
• Overriding C++ classes from Python
• Native Python objects to & from C++
• Embedding the interpreter
• Gil control
Next?
Potential Usage:
• Development Tools
• Unit Testing

Presentations:
• Code & Coffee
• PyOhio Presentation?

You might also like