Pybind 11

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