Source files at github.com:casperdcl/ctypes-demo, based on bitbucket.org:chris_richardson/ctypes-demo

C Source

mylib.c:

#include "math.h"

void add(double *in_data, double *out_data, int n)
{
  int i;

  for (i = 0; i != n; ++i)
      out_data[i] += in_data[i];
}

Compile

gcc -shared -fPIC -o mylib_shared.so mylib.c
nm -a mylib_shared.so  # list symbols

Wrap

ctypes

mylib.py:

from ctypes import CDLL, POINTER, c_double
import numpy as np

MYLIB = CDLL("./mylib_shared.so")


def get_ptr(np_arr, dtype=c_double):
    return np_arr.ctypes.data_as(POINTER(dtype))


def add(x, y, n):
    assert x.dtype == y.dtype == np.float64
    MYLIB.add(get_ptr(x), get_ptr(y), n)

Use as:

import numpy as np
import mylib
x = np.arange(3, dtype=numpy.float64)
y = x.copy()
mylib.add(x, y, 3)
print(y)

cffi

mylib.py:

from cffi import FFI

ffi = FFI()
ffi.cdef("void add(double *in_data, double *out_data, int n);")
MYLIB = ffi.dlopen("./mylib_shared.so")


def add(x, y, n):
    MYLIB.add(
        ffi.cast("double *", x.ctypes.data),
        ffi.cast("double *", y.ctypes.data),
        n)

Alternative in-place:

from cffi import FFI

ffi = FFI()
ffi.cdef("void add(double *in_data, double *out_data, int n);")
ffi.set_source("mylib_shared", open("mylib.c").read())
ffi.compile(verbose=True)

from mylib_shared import lib as MYLIB

add = MYLIB.add

Use as with ctypes.

numba

Compile python itself:

from numba import jit

@jit
def add(...):
    ...

Advanced:

from numba import jit
import numpy as np

@jit
def calculate_distances(n):
    pts = np.random.random((n, 3))
    d = np.zeros((n, n))

    for i in range(n):
        for j in range(n):
            d[i, j] = np.linalg.norm(pts[i,:] - pts[j,:])
    return d

n = 1000
d = calculate_distances(n)

llvm = calculate_distances.inspect_llvm()
for k, v in llvm.items():
    print(k, v)

print(d)

Even more advanced:

# Example demonstrating numba cfunc to provide a compiled callback kernel

from ctypes import POINTER, c_double, c_int
from numba import cfunc, types, carray, jit
import numpy as np


# This is the kernel we want to call from our bigger library
def calculate_distances(_d, _pts, n):
    d = carray(_d, (n, n), dtype=np.float64)
    pts = carray(_pts, (n, 3), dtype=np.float64)

    for i in range(n):
        for j in range(n):
            d[i, j] = np.linalg.norm(pts[i, :] - pts[j, :])

# C signature
sig = types.void(
    types.CPointer(types.double),
    types.CPointer(types.double),
    types.intc)
fn = cfunc(sig, nopython=True)(calculate_distances)  # compile with LLVM

# Take a look at the code
print(fn.inspect_llvm())

# Show the memory address of the function
print('0x%0x'%fn.address)

n = 12
d = np.zeros((n, n), dtype=np.float64)
pts = np.random.random((n, 3))

# Convenient ctypes wrapper allows us to test it from Python
fn.ctypes(d.ctypes.data_as(POINTER(c_double)),
          pts.ctypes.data_as(POINTER(c_double)), n)

np.set_printoptions(precision=2)
print(d)

Wrap C++

code.cpp:

class Foo{
public:
  std::vector<double> dist(){ ... }
};

pybind11

Replacement for boost::python

point_cloud.cpp:

#include <cmath>
#include <vector>

class PointCloud {
public:
  PointCloud(std::vector<double> &points) : _points(points) {}

  std::vector<double> calculate_distances() {
    unsigned int n = _points.size() / 3;
    std::vector<double> result(n * n);

    for (unsigned int i = 0; i != n; ++i)
      for (unsigned int j = 0; j != n; ++j)
        result[i * n + j] = distance(i, j);

    return result;
  }

private:
  double distance(unsigned int i, unsigned int j) {
    double d = 0.0;
    for (unsigned int k = 0; k != 3; ++k) {
      double d0 = _points[i * 3 + k] - _points[j * 3 + k];
      d += d0 * d0;
    }
    return std::sqrt(d);
  }

  std::vector<double> _points;
};

wrap_point_cloud.cpp:

#include <point_cloud.cpp>  // bad practice ...
// ... you should have a header and link libs instead, but you get the idea
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

// Wrappers using pybind11
namespace py = pybind11;
PYBIND11_MODULE(point_cloud, m) {
  py::class_<PointCloud>(m, "PointCloud")  // class
      .def(py::init<std::vector<double> &>())  // initialiser
      .def("calculate_distances", &PointCloud::calculate_distances);
}

setup.py:

from distutils.core import setup
from distutils.extension import Extension

import pybind11
pybind11_include = pybind11.get_include()

setup(name='point_cloud',
      version='1.0',
      ext_modules=[Extension(
          "point_cloud",
          ['wrap_point_cloud.cpp'],
          language='c++',
          #include_dirs=[pybind11_include, '/usr/include/eigen3'],
          include_dirs=[pybind11_include],
          extra_compile_args=['-std=c++11'])])

Use:

python setup.py build
PYTHONPATH="$PYTHONPATH:$PWD/build/lib*" python -c "
from point_cloud import PointCloud
import numpy as np

n = 5
pts = np.random.random((n, 3))

p = PointCloud(pts.flatten().tolist())  # list -> std::vector
d_list = p.calculate_distances()  # std::vector -> list
d = np.array(d_list).reshape((n, n))  # Reshape with numpy

print(d)
"

swig

Best for multi-language interfaces, not great if only wrapping for Python.

pycuda

Similar to in-place cffi.