user1313527
user1313527

Reputation: 141

Adding the __add__ operator to a Cython wrapper class?

The following C++ code goes off the standard Cython rectangle example, with an arbitrary added "+" operator:

#include "Rectangle.h"

using namespace shapes;

Rectangle::Rectangle(int X0, int Y0, int X1, int Y1)
{
    x0 = X0;
    y0 = Y0;
    x1 = X1;
    y1 = Y1;
}

Rectangle::~Rectangle() {}

int Rectangle::getLength()
{
    return (x1 - x0);
}


Rectangle operator+(const Rectangle &r1, const Rectangle &r2)
{
    return Rectangle(r1.X0 + r2.X0, r1.Y0 + r2.Y0, r1.X1 + r2.X1, r1.Y1 + r2.Y1)
}

This goes with the Cython C++ class definition:

cdef extern from "Rectangle.h" namespace "shapes":
    cdef cppclass Rectangle:
        Rectangle(int, int, int, int) except +
        int x0, y0, x1, y1
        int getLength()
        Rectangle operator+(Rectangle) nogil

The only way we've figured out of doing this is by the following Cython code:

cdef class PyRectangle:
    cdef Rectangle *thisptr      # hold a C++ instance which we're wrapping
    def __cinit__(self, int x0=0, int y0=0, int x1=0, int y1=0):
        if x0 == 0:
            self.thisptr = NULL
        else:
            self.thisptr = new Rectangle(x0, y0, x1, y1)
    def __dealloc__(self):
        del self.thisptr
    def getLength(self):
        return self.thisptr.getLength()
    def __add__(self, other):
        cdef Rectangle rect = deref(self.thisptr) + deref(other.thisptr)
        cdef Rectangle* ptr_rect = new Rectangle(rect.x0, rect.y0, rect.x1, rect.y1)
        ret = PyRectangle()
        ret.thisptr = ptr_rect
        return ret

This is not very optimal, since we have an extra copy in __add__, and the code is not very simple/short either. This is for wrapping an external library, so we can't simply define any new constructors to Rectangle, and we can't rewrite the addition at the Cython level.

We thought that we could simply write something like:

ret = PyRectangle()
deref(ret.thisptr) = deref(self.thisptr) + deref(other.thisptr)
return ret

But this gives the error "Cannot assign to or delete this."

Is there a more preferred way of doing this sort of thing in Cython? The solution we found is not viable in our code.

Upvotes: 3

Views: 775

Answers (1)

robertwb
robertwb

Reputation: 5104

For pointers, x[0] is the same as deref(x), so you can instead write

ret.thisptr[0] = self.thisptr[0] + other.thisptr[0]

Note also that if the object being wrapped has a nullary constructor, there's no need for pointers at all, simply do

cdef class PyRectangle:
    cdef Rectangle c_rect
    def __init__(self, int x0=0, int y0=0, int x1=0, int y1=0):
        self.c_rect = Rectangle(x0, y0, x1, y1) 
    # no __dealloc__ needed
    def __add__(PyRectangle left, PyRectangle right):
        PyRectangle ret = PyRectangle()
        ret.c_rect = left.c_rect + right.c_rect
        return ret

I find it handy in this case to add a static method

cdef class PyRectangle:
    [...]
    @staticmethod
    cdef create(Rectangle r):
         PyRectangle ret = PyRectangle()
         ret.c_rect = r
         return ret

then you can simply do

def __add__(PyRectangle left, PyRectangle right):
    return PyRectangle.create(left.c_rect + right.c_rect)

Upvotes: 3

Related Questions