如何为Cython包装类添加__add__运算符?

3 投票
1 回答
764 浏览
提问于 2025-04-29 20:15

下面这段C++代码是基于标准的Cython矩形示例,额外添加了一个“+”运算符:

#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)
}

这段代码对应的Cython C++类定义如下:

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

我们找到的唯一实现方法是下面这段Cython代码:

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

不过这样做并不是很理想,因为在__add__里多了一次复制,而且代码也不够简单和简短。这是为了包装一个外部库,所以我们不能随便给Rectangle定义新的构造函数,也不能在Cython层面重写加法运算。

我们曾想过可以简单地写成这样:

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

但这样会出现错误“无法赋值或删除这个。”

在Cython中有没有更好的方法来实现这种功能?我们找到的解决方案在我们的代码中不可行。

暂无标签

1 个回答

3

对于指针来说,x[0]deref(x) 是一样的,所以你可以直接写成

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

另外,如果被包装的对象有一个不带参数的构造函数,那就根本不需要指针,直接这样做就可以了

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

在这种情况下,我觉得添加一个静态方法很方便

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

然后你就可以简单地这样做

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

撰写回答