Last active
September 22, 2020 21:40
-
-
Save derrickturk/781e7ed200c18f9a43aa4900d9f367e9 to your computer and use it in GitHub Desktop.
A minimal(?) Python module in C++, with MinGW makefile
This file contains 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
#define PY_SSIZE_T_CLEAN | |
// wtf | |
#define _hypot hypot | |
#include <Python.h> | |
#include <memory> | |
#include <utility> | |
#include <string> | |
#include <sstream> | |
#include <exception> | |
using namespace std::string_literals; | |
namespace { | |
class node { | |
public: | |
node(int root_val) noexcept | |
: val_(root_val), left_(), right_() { } | |
void insert(int val) | |
{ | |
if (val <= val_) { | |
if (left_) | |
left_->insert(val); | |
else | |
left_ = std::make_unique<node>(val); | |
} else { | |
if (right_) | |
right_->insert(val); | |
else | |
right_ = std::make_unique<node>(val); | |
} | |
} | |
void reverse() noexcept | |
{ | |
if (left_) | |
left_->reverse(); | |
if (right_) | |
right_->reverse(); | |
std::swap(left_, right_); | |
} | |
template<class F> | |
void inorder(F&& fn) noexcept(noexcept(fn(0))) | |
{ | |
if (left_) | |
left_->inorder(std::forward<F>(fn)); | |
fn(val_); | |
if (right_) | |
right_->inorder(std::forward<F>(fn)); | |
} | |
std::string pprint() | |
{ | |
auto left = left_ ? left_->pprint() : "empty"s; | |
auto right = right_ ? right_->pprint() : "empty"s; | |
auto s = std::stringstream(); | |
s << "Tree(" << val_ << ", left = " << left | |
<< ", right = " << right << ')'; | |
return s.str(); | |
} | |
private: | |
int val_; | |
std::unique_ptr<node> left_; | |
std::unique_ptr<node> right_; | |
}; | |
struct PyTree { | |
PyObject_HEAD | |
node root; | |
}; | |
/* for the same reasons as PyO3, I've made the same decision as PyO3: | |
* just provide a __new__ with arguments, not an __init__ | |
*/ | |
extern "C" PyObject *PyTree_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) | |
{ | |
static const char *kws[] = { "val", nullptr }; | |
PyTree *self = reinterpret_cast<PyTree *>(type->tp_alloc(type, 0)); | |
if (!self) | |
return nullptr; | |
int val; | |
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i", | |
const_cast<char **>(kws), &val)) { | |
Py_DECREF(self); | |
return nullptr; | |
} | |
new (&self->root) node(val); | |
return reinterpret_cast<PyObject *>(self); | |
} | |
extern "C" void PyTree_dealloc(PyTree *self) | |
{ | |
self->root.~node(); | |
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject *>(self)); | |
} | |
extern "C" PyObject *PyTree_str(PyTree *self) | |
{ | |
try { | |
auto str = self->root.pprint(); | |
return PyUnicode_FromString(str.c_str()); | |
} catch (...) { | |
PyErr_SetString(PyExc_RuntimeError, "stringification failed"); | |
return nullptr; | |
} | |
} | |
// METH_VARARGS | |
extern "C" PyObject *PyTree_insert(PyTree *self, PyObject *args) | |
{ | |
int val; | |
if (!PyArg_ParseTuple(args, "i", &val)) | |
return nullptr; | |
try { | |
self->root.insert(val); | |
} catch (...) { | |
PyErr_SetString(PyExc_RuntimeError, "insertion failed"); | |
return nullptr; | |
} | |
Py_INCREF(Py_None); | |
return Py_None; | |
} | |
// METH_NOARGS | |
extern "C" PyObject *PyTree_reverse(PyTree *self, PyObject *) | |
{ | |
self->root.reverse(); | |
Py_INCREF(Py_None); | |
return Py_None; | |
} | |
// METH_VARARGS | |
extern "C" PyObject *PyTree_inorder(PyTree *self, PyObject *args) | |
{ | |
PyObject *fn; | |
if (!PyArg_ParseTuple(args, "O", &fn)) | |
return nullptr; | |
if (!PyCallable_Check(fn)) { | |
PyErr_SetString(PyExc_TypeError, "parameter must be callable"); | |
return nullptr; | |
} | |
try { | |
self->root.inorder([=](int val) { | |
auto arglist = Py_BuildValue("(i)", val); | |
auto result = PyObject_CallObject(fn, arglist); | |
Py_DECREF(arglist); | |
if (!result) { | |
/* throw a meaningless exception; | |
* the Python machinery has more detail about the error | |
*/ | |
throw std::exception(); | |
} | |
Py_DECREF(result); | |
}); | |
} catch (const std::exception&) { | |
return nullptr; | |
} | |
Py_INCREF(Py_None); | |
return Py_None; | |
} | |
PyMethodDef PyTree_methods[] = { | |
{ "insert", reinterpret_cast<PyCFunction>(PyTree_insert), | |
METH_VARARGS, "insert a value" }, | |
{ "reverse", reinterpret_cast<PyCFunction>(PyTree_reverse), | |
METH_NOARGS, "reverse the tree" }, | |
{ "inorder", reinterpret_cast<PyCFunction>(PyTree_inorder), | |
METH_VARARGS, "traverse inorder with a callback" }, | |
{ nullptr }, | |
}; | |
PyTypeObject PyTreeType = { | |
// C++20 sez designated initializers are all-or-nothing | |
// Guido sez leave off the comma... | |
.ob_base = PyVarObject_HEAD_INIT(nullptr, 0) | |
.tp_name = "cpppyobj.PyTree", | |
.tp_basicsize = sizeof(PyTree), | |
.tp_itemsize = 0, | |
.tp_dealloc = reinterpret_cast<destructor>(PyTree_dealloc), | |
.tp_str = reinterpret_cast<reprfunc>(PyTree_str), | |
.tp_flags = Py_TPFLAGS_DEFAULT, | |
.tp_doc = "A tree, yo", | |
.tp_methods = PyTree_methods, | |
.tp_new = PyTree_new, | |
}; | |
struct PyModuleDef module = { | |
.m_base = PyModuleDef_HEAD_INIT, | |
.m_name = "cpppyobj", | |
.m_doc = "a module with an object", | |
.m_size = -1, | |
}; | |
} | |
PyMODINIT_FUNC | |
PyInit_cpppyobj(void) | |
{ | |
if (PyType_Ready(&PyTreeType) < 0) | |
return nullptr; | |
auto m = PyModule_Create(&module); | |
if (!m) | |
return nullptr; | |
Py_INCREF(&PyTreeType); | |
if (PyModule_AddObject(m, "PyTree", | |
reinterpret_cast<PyObject*>(&PyTreeType)) < 0) { | |
Py_DECREF(&PyTreeType); | |
Py_DECREF(m); | |
return nullptr; | |
} | |
return m; | |
} |
This file contains 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
#define PY_SSIZE_T_CLEAN | |
// wtf | |
#define _hypot hypot | |
#include <Python.h> | |
namespace { | |
extern "C" PyObject *add(PyObject *module, PyObject *args) | |
{ | |
PyObject *lhs, *rhs; | |
if (!PyArg_ParseTuple(args, "OO", &lhs, &rhs)) | |
return NULL; | |
return PyNumber_Add(lhs, rhs); | |
} | |
extern "C" PyObject *addi(PyObject *module, PyObject *args) | |
{ | |
long lhs, rhs; | |
if (!PyArg_ParseTuple(args, "ll", &lhs, &rhs)) | |
return NULL; | |
return PyLong_FromLong(lhs + rhs); | |
} | |
PyMethodDef methods[] = { | |
{ "add", add, METH_VARARGS, "add two things" }, | |
{ "addi", addi, METH_VARARGS, "add two integers" }, | |
{ NULL, NULL, 0, NULL }, | |
}; | |
struct PyModuleDef module = { | |
PyModuleDef_HEAD_INIT, | |
"cpppytest", | |
NULL, | |
-1, | |
methods | |
}; | |
} | |
PyMODINIT_FUNC | |
PyInit_cpppytest(void) | |
{ | |
return PyModule_Create(&module); | |
} |
This file contains 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 cpppyobj import PyTree | |
if __name__ == '__main__': | |
t = PyTree(33) | |
t.insert(77) | |
t.insert(23) | |
t.insert(35) | |
t.insert(99) | |
t.insert(45) | |
t.insert(3) | |
print(t) | |
t.inorder(lambda x: print(x * 37)) | |
t.reverse() | |
t.inorder(lambda x: print(x * 37)) |
This file contains 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
CXX=g++ | |
CXXOPTS=-Wall -pedantic -std=c++2a -O2 -static | |
PYROOT=C:/Users/Derrick/local | |
PYVER=36 | |
PYINCLUDEDIR=$(PYROOT)/Python$(PYVER)/include | |
PYLIBDIR=$(PYROOT)/Python$(PYVER)/libs | |
ALL: cpppytest.pyd cpppyobj.pyd | |
%.pyd: %.cpp | |
$(CXX) $(CXXOPTS) -shared -I$(PYINCLUDEDIR) -L$(PYLIBDIR) -o $@ $^ -lpython$(PYVER) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment