Skip to content

Instantly share code, notes, and snippets.

@splinterofchaos
Last active August 29, 2015 14:10
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save splinterofchaos/b099149a701edfa5948f to your computer and use it in GitHub Desktop.
Python API in C++
#include <Python.h>
#include "Py.h"
PyObject *count(PyObject *self, PyObject *args)
{
static int i = 0;
PySys_WriteStdout("%i\n", ++i); // Just like printf.
return PyInt_FromLong(i);
}
static PyMethodDef countMethods[] = {
MethodDef("count", "Returns the number of times called.", count),
{NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC initcount()
{
PyObject *m = Py_InitModule("count", countMethods);
}
#ifndef PY_H
#define PY_H
#include <Python.h>
#include <type_traits>
#include <utility>
#include <string>
template<typename R, typename...X>
constexpr int arity(R(*)(X...)) {
return sizeof...(X);
}
template<typename R, typename...X>
constexpr bool returns_PyObject(R(*)(X...)) {
// Value is either a PyObject, or a subclass of one.
return std::is_convertible<R, PyObject *>::value;
}
template<typename R, typename...X>
constexpr bool is_PyCFunction(R(*)(X...)) {
return false;
}
template<>
constexpr bool is_PyCFunction(PyCFunction) {
return true;
}
template<typename F>
constexpr int MethodType(F f) {
return arity(f) == 3 ? METH_KEYWORDS
: is_PyCFunction(f) ? METH_VARARGS
: METH_O;
}
template<typename F>
constexpr PyMethodDef MethodDef(const char *name, const char *doc,
int type, F f)
{
static_assert(arity(F()) == 2 || arity(F()) == 3,
"Methods must have an arity of 2 or 3");
static_assert(returns_PyObject(F()), "Methods must return a PyObject *.");
return {name, (PyCFunction)f, type, doc};
}
template<typename F>
constexpr PyMethodDef MethodDef(const char *name, const char *doc, const F f)
{
return MethodDef(name, doc, MethodType(f), f);
}
template<typename F>
void Register(initproc &proc, F f)
{
static_assert(arity(F()) == 3, "__init__ must have an arity of 2 or 3");
static_assert(is_object_method(F()),
"First argument must be PyObject-compatible.");
proc = (initproc) f;
}
template<typename F>
void Register(reprfunc &proc, F f)
{
static_assert(arity(F()) == 1, "__str/repr__ must have an arity of 1");
static_assert(is_object_method(F()),
"First argument must be PyObject-compatible.");
proc = (reprfunc) f;
}
template<typename T>
struct Extention : PyObject
{
static PyTypeObject type;
T ext;
T &get() & {
return this->ext;
}
const T &get() const & {
return this->ext;
}
T *ptr() & {
return &this->ext;
}
const T *ptr() const & {
return &this->ext;
}
};
template<typename T,
typename = std::enable_if_t<std::is_default_constructible<T>::value>>
newfunc default_new()
{
return [](PyTypeObject *type, PyObject *args, PyObject *kwds)
{
using Self = Extention<T>;
Self *self = (Self *) type->tp_alloc(type, 0);
if (self)
new (self->ptr()) T();
return (PyObject *) self;
};
}
template<typename T,
typename = std::enable_if_t<!std::is_default_constructible<T>::value>>
auto default_new() {
return [](PyTypeObject *type, PyObject *args, PyObject *kwds)
{
return type->tp_alloc(type, 0);
};
}
template<typename T>
PyTypeObject Extention<T>::type = {
PyObject_HEAD_INIT(NULL)
0, // ob_size
0, // tp_name
sizeof(Extention<T>), // tp_basicsize
0, // tp_itemsize
destructor([](PyObject *self) {
((Extention *) self)->get().T::~T();
self->ob_type->tp_free(self);
}),
0, // tp_print
0, // tp_getattr
0, // tp_setattr
0, // tp_compare
0, // tp_repr
0, // tp_as_number
0, // tp_as_sequence
0, // tp_as_mapping
0, // tp_hash
0, // tp_call
0, // tp_str
0, // tp_getattro
0, // tp_setattro
0, // tp_as_buffer
Py_TPFLAGS_DEFAULT, // tp_flags
0, // tp_doc
0, // tp_traverse
0, // tp_clear
0, // tp_richcompare
0, // tp_weaklistoffset
0, // tp_iter
0, // tp_iternext
0, // tp_methods
0, // tp_members
0, // tp_getset
0, // tp_base
0, // tp_dict
0, // tp_descr_get
0, // tp_descr_set
0, // tp_dictoffset
0, // tp_init
0, // tp_alloc
default_new<T>(), // tp_new
};
#endif // PY_H
from distutils.core import setup, Extension
cnt = Extension('count',
sources = ['countmodule.cpp'],
extra_compile_args = ['--std=c++14'])
vec = Extension('vec',
sources = ['vecmodule.cpp'],
extra_compile_args = ['--std=c++14'])
setup (name = 'cpp',
version = '1.0',
ext_modules = [cnt, vec])
#ifndef PY_TUPLE_H
#define PY_TUPLE_H
#include <Python.h>
#include <tuple>
#include <utility>
template<char...cs>
using CharList = std::integer_sequence<char, cs...>;
template<typename...T>
struct CharListConcat;
template<typename T>
struct CharListConcat<T> {
using type = T;
};
template<typename...U, char...cs, char...cs2>
struct CharListConcat<CharList<cs...>, CharList<cs2...>, U...> {
using type = typename CharListConcat<CharList<cs..., cs2...>, U...>::type;
};
template<typename...T>
using CharListConcat_t = typename CharListConcat<T...>::type;
/// A type to signify the rest of the parameters are optional.
struct Optional { };
template<typename...T>
struct PTCharListOf { };
template<> struct PTCharListOf<const char *> {
using type = CharList<'s'>;
};
template<> struct PTCharListOf<Py_buffer> {
using type = CharList<'s', '*'>;
};
template<> struct PTCharListOf<unsigned char> {
using type = CharList<'b'>;
};
template<> struct PTCharListOf<unsigned int> {
using type = CharList<'h'>;
};
template<> struct PTCharListOf<int> {
using type = CharList<'i'>;
};
template<> struct PTCharListOf<Py_ssize_t> {
using type = CharList<'n'>;
};
template<> struct PTCharListOf<float> {
using type = CharList<'f'>;
};
template<> struct PTCharListOf<double> {
using type = CharList<'d'>;
};
template<> struct PTCharListOf<PyObject *> {
using type = CharList<'O'>;
};
template<> struct PTCharListOf<PyStringObject *> {
using type = CharList<'S'>;
};
template<>
struct PTCharListOf<Optional> {
using type = CharList<'|'>;
};
template<typename...Ts>
struct PTCharListOf<std::tuple<Ts...>> {
using type =
CharListConcat_t<CharList<'('>,
typename PTCharListOf<std::decay_t<Ts>>::type...,
CharList<')'>>;
};
template<typename T>
using PTCharListOf_t = typename PTCharListOf<T>::type;
template<typename...Ts>
struct ParseTupleBuilder { };
template<typename CL, typename T, typename...Ts>
struct ParseTupleBuilder<CL, T, Ts...> {
using type = ParseTupleBuilder<CharListConcat_t<CL, PTCharListOf_t<T>>,
Ts...>;
constexpr static const char *fmt = type::fmt;
};
template<char...cs>
struct ParseTupleBuilder<CharList<cs...>> {
using type = CharList<cs...>;
static const char fmt[sizeof...(cs) + 1];
};
template<char...cs>
const char ParseTupleBuilder<CharList<cs...>>::fmt[] = { cs..., '\0' };
template<typename...Ts>
constexpr const char *ParseTupleFormat(Ts...) {
return ParseTupleBuilder<CharList<>, std::decay_t<Ts>...>::fmt;
}
template<typename F, typename T, size_t...Is>
decltype(auto) apply_tuple(F&& f, T&& t, std::index_sequence<Is...>) {
return std::forward<F>(f)(std::get<Is>(std::forward<T>(t))...);
}
template<typename F, typename T, size_t...Is>
decltype(auto) map_tuple(F&& f, T&& t, std::index_sequence<Is...>) {
return std::make_tuple(std::forward<F>(f)(std::get<Is>(std::forward<T>(t)))...);
}
template<typename F, typename...Ts,
typename Is = std::make_index_sequence<sizeof...(Ts)>>
decltype(auto) map_tuple(F&& f, std::tuple<Ts...> &t) {
return map_tuple(std::forward<F>(f), t, Is());
}
template<typename...Bound,
typename Indicies = std::make_index_sequence<sizeof...(Bound)>>
bool ParseTuple_impl(std::tuple<Bound...> &&bound) {
return apply_tuple(PyArg_ParseTuple, bound, Indicies());
}
template<typename...Bound, typename Arg, typename...Args>
bool ParseTuple_impl(std::tuple<Bound...> &&bound, Arg &a, Args &...as) {
return ParseTuple_impl(std::tuple_cat(std::move(bound), std::make_tuple(&a)),
as...);
}
template<typename...Bound, typename...Args>
bool ParseTuple_impl(std::tuple<Bound...> &&bound, Optional, Args &...as) {
return ParseTuple_impl(std::move(bound), as...);
}
template<typename...Bound, typename...Ts, typename...Args>
bool ParseTuple_impl(std::tuple<Bound...> &&bound, std::tuple<Ts &...> &t,
Args &...as) {
auto &&mapped = map_tuple([](auto &x) { return &x; }, t);
return ParseTuple_impl(std::tuple_cat(bound, std::move(mapped)),
as...);
}
template<typename...Args>
bool ParseTuple(PyObject *args, Args &&...as) {
return ParseTuple_impl(std::make_tuple(args, ParseTupleFormat(as...)),
as...);
}
template<typename...Bound,
typename Indicies = std::make_index_sequence<sizeof...(Bound)>>
PyObject *BuildValue_impl(std::tuple<Bound...> &&bound) {
return apply_tuple(Py_BuildValue, bound, Indicies());
}
template<typename...Bound, typename Arg, typename...Args>
PyObject *BuildValue_impl(std::tuple<Bound...> &&bound, Arg a, Args ...as) {
return BuildValue_impl(std::tuple_cat(std::move(bound), std::make_tuple(a)),
as...);
}
template<typename...Bound, typename...Args>
PyObject *BuildValue_impl(std::tuple<Bound...> &&bound, Optional, Args &...as) {
return BuildValue_impl(std::move(bound), as...);
}
template<typename...Bound, typename...Ts, typename...Args>
PyObject *BuildValue_impl(std::tuple<Bound...> &&bound, std::tuple<Ts...> &t,
Args &...as) {
return BuildValue_impl(std::tuple_cat(bound, std::move(t)), as...);
}
template<typename...Args>
PyObject *BuildValue(Args &...as) {
return BuildValue_impl(std::make_tuple(ParseTupleFormat(as...)),
as...);
}
#endif // PY_TUPLE_H
#include <Python.h>
#include "Py.h"
#include "Tuple.h"
struct Vec {
float x, y, z;
};
using PyVec = Extention<Vec>;
int init_vec(PyVec *self, PyObject *args, PyObject *)
{
Vec &v = self->get();
if (!ParseTuple(args, v.x, v.y, v.z))
return -1;
return 0;
}
PyObject *vec_str(PyVec *self)
{
return PyString_FromString(("<" + std::to_string(self->get().x) +
", " + std::to_string(self->get().y) +
", " + std::to_string(self->get().z) +
">").c_str());
}
PyObject *cross(PyObject *self, PyObject *args)
{
PyObject *o1, *o2;
if (!ParseTuple(args, o1, o2))
return nullptr;
// Ensure o1 and 2 are the right types.
if (!PyType_IsSubtype(o1->ob_type, &PyVec::type) ||
!PyType_IsSubtype(o2->ob_type, &PyVec::type))
return nullptr;
Vec &v = ((PyVec *) o1)->get(), &w = ((PyVec *) o2)->get();
float i = v.y*w.z - v.z*w.y;
float j = v.z*w.x - v.x*w.z;
float k = v.x*w.y - v.y*w.x;
PyObject *ret = PyVec::type.tp_new(&PyVec::type, nullptr, nullptr);
PyObject *val = BuildValue(i, j, k);
init_vec((PyVec *) ret, val, nullptr);
Py_DECREF(val);
return ret;
}
static PyMethodDef vecMethods[] = {
MethodDef("cross", "Returns the cross product of two 3D vectors.", cross),
{NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC initvec()
{
PyVec::type.tp_name = "vec.Vec";
PyVec::type.tp_init = (initproc) init_vec;
PyVec::type.tp_repr = PyVec::type.tp_str = (reprfunc) vec_str;
if (PyType_Ready(&PyVec::type) < 0)
return;
PyObject *m = Py_InitModule("vec", vecMethods);
if (!m)
return;
Py_INCREF(&PyVec::type);
PyModule_AddObject(m, "Vec", (PyObject *) &PyVec::type);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment