【问题标题】:Returning Object Pointer: 'Cannot convert 'Coordinate *' to Python object'返回对象指针:'无法将'坐标 *' 转换为 Python 对象'
【发布时间】:2018-04-12 10:02:06
【问题描述】:

我目前正在尝试对我的 c++ 类进行 cythonize 以便在 python 中使用,并且我从我的一个简单类开始,但我坚持返回一个指针。我试图解决这个问题的方法是添加一个复制构造函数并返回 C++ 类的 Python 版本,但没有成功。

我收到以下错误:

Error compiling Cython file:
------------------------------------------------------------
...

    def bearing(self, Coordinate *coordinate):
        return self.thisptr.bearing(coordinate)

    def destination(self, double bearing, double distance):
        return PyCCoordinate(self.thisptr.destination(bearing, distance))
                                                    ^
------------------------------------------------------------

./coordinate.pyx:32:53: Cannot convert 'Coordinate *' to Python object

这是我的文件

坐标.h

class Coordinate {
private:
    double m_x = 0;
    double m_y = 0;
public:
    Coordinate(const double x, const double y);

    Coordinate(const Coordinate *coord);

    void setX(const double value);

    void setY(const double value);

    double getX();

    double getY();

    double distance(Coordinate *coord);

    double bearing(Coordinate *coord);

    Coordinate *destination(const double bearing, const double distance);
};

坐标.cpp

#include <cmath>
#include "coordinate.h"

Coordinate::Coordinate(const double x, const double y) {
    m_x = x;
    m_y = y;
}

Coordinate::Coordinate(const Coordinate *coord) {
    m_x = coord->x;
    m_y = coord->y;
}


void Coordinate::setX(const double value) {
    m_x = value;
}

void Coordinate::setY(const double value) {
    m_y = value;
}

double Coordinate::getX() {
    return m_x;
}

double Coordinate::getY() {
    return m_y;
}

double Coordinate::distance(Coordinate *coord) {
    return sqrt(pow(m_x - coord->getX(), 2.0) + pow(m_y - coord->getY(), 2.0));
}

double Coordinate::bearing(Coordinate *coord) {
    const double len_x = coord->getX() - m_x;
    const double len_y = coord->getY() - m_y;
    double res = atan2(len_y, len_x);
    if (res < 0.0) {
        res += 2.0 * M_PI;
    }
    return res;
}

Coordinate *Coordinate::destination(const double bearing, const double distance) {
    double new_x = m_x + cos(bearing) * distance;
    double new_y = m_y + sin(bearing) * distance;
    return new Coordinate(new_x, new_y);
}

坐标.pxy

cdef extern from "coordinate.h":
    cdef cppclass Coordinate:
        Coordinate(const double x, const double y) except +
        Coordinate(const Coordinate *coord) except +

        void setX(const double value)
        void setY(const double value)
        double getX()
        double getY()
        double distance(Coordinate *coord)
        double bearing(Coordinate *coord)
        Coordinate *destination(const double bearing, const double distance)

cdef class PyCCoordinate:
    cdef Coordinate *thisptr
    def __cinit__(self, double x, double y):
        self.thisptr = new Coordinate(x,y)
    def __cinit__(self, Coordinate* coord):
        self.thisptr = new Coordinate(coord)
    def __dealloc__(self):
        del self.thisptr        

    def distance(self, Coordinate *coordinate):
        return self.thisptr.distance(coordinate)

    def bearing(self, Coordinate *coordinate):
        return self.thisptr.bearing(coordinate)

    def destination(self, double bearing, double distance):
        return PyCCoordinate(self.thisptr.destination(bearing, distance))

【问题讨论】:

  • 你能添加一个 setup.py 文件吗?对于这种事情来说,在例如创建一个 repo 是最简单的。 github,其中一个可以克隆,因此我们不需要从 SO 复制/粘贴文件。我的猜测是您在某处缺少中间变量的类型,但我需要查看 cython 生成的代码以确定。
  • 这显然不是一个最小的示例,减少不需要的代码量是有意义的。

标签: python c++ cython cythonize


【解决方案1】:

一个问题是 cython 语法有些误导:如果将 def-function 定义为例如(因为您的示例不是最小的,我编造了一个不相关的示例):

def twice(double d):
   return 2.0*d

然后参数不是作为 C-double 而是作为通常的 Python 对象传递给这个 (python) 函数。但是,早期绑定将导致 cython 尝试在运行时通过 __pyx_PyFloat_AsDouble 将此 Python 对象转换为 C-double,这是调用该函数时的第一件事 - 这一切都发生在幕后,所以作为编码器,你有这种欺骗性的填充物,你实际上会传递一个 double 给函数。

但是,这种自动转换仅适用于某些类型 - 最突出的是 doubleintcdef-classes 等。对于其他类型,这是不可能的,例如对于原始指针(这也意味着指向自定义 cpp 类的指针) - 没有像 __pyx_PyFloat_AsDouble 这样的东西。

例如

def twice(double *d):
   pass

不能被cythonized,因为原始指针不能自动从/转换为python-object,这是python-function所需要的。

可以使用原始指针定义 cdef 函数,因为它们只不过是简单的 C 函数,因此

cdef twice(double *d):
   pass

会编译。但是,这对 __cinit__ 没有帮助,因为它必须是 def 函数。

不幸的是,Cython 不会在您的代码中显示所有错误,只会显示它找到的第一个错误 - 否则它会显示您的所有 defs 和 Coordinate *coordinate 都无效。

如何解决呢?基本上你应该在签名中使用cdef-wrapper-class PyCCoordinate,然后使用thisptr-进行计算,例如:

def distance(self, PyCCoordinate coordinate):
    return self.thisptr.distance(coordinate.thisptr)

该解决方案显然不适用于对象的构造,例如方法destination - 您必须先构造PyCCoordinate-object,然后才能使用它!一个可能的解决方案是有一个构造函数,它将构造一个包装对象,thisptrNULL,调用它并手动设置这个指针,类似于

cdef class PyCCoordinate:
    cdef Coordinate *thisptr
    def __cinit__(self):
        pass

    def __dealloc__(self):
        del self.thisptr        

    def destination(self, double bearing, double distance):
        cdef PyCCoordinate res=PyCCoordinate()
        res.thisptr=self.thisptr.destination(bearing, distance)
        return res

另一个问题:cython(也是python),不像c++,不知道重载,所以不能定义两个不同的构造函数(也不能定义一个私有的),所以dispatch必须手动完成,例如:

cdef class PyCCoordinate:
    cdef Coordinate *thisptr

    def __cinit__(self, x=None, y=None):
        if x is None or y is None:#default, "private" constructor
           pass     #thisptr is initialized to NULL
        else:
           self.thisptr = new Coordinate(x,y) #

    def __dealloc__(self):
        del self.thisptr        

    def destination(self, double bearing, double distance):
        cdef PyCCoordinate res=PyCCoordinate()
        res.thisptr=self.thisptr.destination(bearing, distance)
        return res

【讨论】:

  • 非常感谢您的广泛回答。我设法把它编译了,它做了 ist 应该做的事情。但对我来说,有很多包装和对象构造使得使用 cpp 的性能增益消失(我猜,但没有测量它)。我有 10 个想要在 cpp 中实现的 py 模块。本来打算一个一个来做的,但是我觉得最好还是直接在cpp类中实现所有模块,只有一个小类,就是py和cpp之间的接口
  • @Westranger 不知道代码很难说,瓶颈是什么。通常,调用 python 函数会产生相当大的开销,因此人们会尝试减少这些调用的数量——因此,尽量减少包装代码量的策略一点也不坏。在 c++ 中获得高效的代码比在 cython 中更容易(至少如果你精通 c++ 而不是 cython)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多