Skip to content

Instantly share code, notes, and snippets.

@derrickturk
Last active September 22, 2020 21:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save derrickturk/781e7ed200c18f9a43aa4900d9f367e9 to your computer and use it in GitHub Desktop.
Save derrickturk/781e7ed200c18f9a43aa4900d9f367e9 to your computer and use it in GitHub Desktop.
A minimal(?) Python module in C++, with MinGW makefile
#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;
}
#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);
}
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))
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