-
-
Save splinterofchaos/b099149a701edfa5948f to your computer and use it in GitHub Desktop.
Python API in C++
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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