Cython example with more than one class

孤者浪人 提交于 2020-01-24 22:21:27

问题


I ask your help, because I'm trying to run a cython example a bit more complex than the one with one class that is possible to find in many tutorials (e.g. this guide ). I haven't found any "more advanced" tutorial, so I hope this question will be useful also for people that are trying to learn it a bit more in depth.

I will write here the steps I took, hoping that someone will tell me where is my mistake.

I have a Rectangle c++ class (I put here just the .h file to make it shorter):

#ifndef RECTANGLE_H
#define RECTANGLE_H
namespace shapes { 
    class Rectangle {
        public:
            int x0, y0, x1, y1;
            Rectangle();
            Rectangle(int x0, int y0, int x1, int y1);
            ~Rectangle();
            int getArea();
    };
}
#endif

and a Group2 class. A very simple example class, whose constructor take as input 2 Rectangles:

#ifndef GROUP4_H
#define GROUP4_H
#include "Rectangle.h"
namespace shapes{
    class Group2 {
    public:
        Rectangle rect0, rect1, rect2, rect3 ;
        Group2();
        Group2(Rectangle rect0, Rectangle rect1);
        ~Group2();
        void getAreas(int *area0, int *area1);
    };
}
#endif

Then I create a grp2.pyx file with the definition of both Rectangle and Group2 class:

#RECTANGLE
cdef extern from "Rectangle.h" namespace "shapes":
    cdef cppclass Rectangle:
        Rectangle() except +
        Rectangle(int, int, int, int) except +
        int x0, y0, x1, y1
        int getArea()

cdef class PyRectangle:
    cdef Rectangle c_rect
    def __cinit__(self, int x0, int y0, int x1, int y1):
        self.c_rect = Rectangle(x0, y0, x1, y1)
    def get_area(self):
        return self.c_rect.getArea()

# GROUP2
cdef extern from "Group2.h" namespace "shapes":
    cdef cppclass Group2:
        Group2() except +
        Group2(Rectangle rect0, Rectangle rect1) except +
        void getAreas(int *area0, int *area1)
cdef class PyGroup2:
    cdef Group2 c_group2
    def __cinit__(self, Rectangle rect0, Rectangle rect1):
        self.c_group2 = Group2(rect0, rect1)
    def get_areas(self):
        cdef int area0, area1
        self.c_group2.getAreas(&area0, &area1)
        return area0, area1

Then I compile those two class in a static c++ library with command line:

gcc -c -fPIC Group2.cpp Rectangle.cpp

and

ar rcs libexample.a Group2.o Rectangle.o

To finish i create the cython setup.py file that i call from command line:

from distutils.core import setup, Extension
from Cython.Build import cythonize

setup(ext_modules = cythonize(Extension(
           name="grp2",                                # the extension name
           sources=["grp2.pyx"], # the Cython source and
           libraries=["example"],
           library_dirs=["lib"],
           include_dirs=["lib"],
                                                  # additional C++ source files
           language="c++",                        # generate and compile C++ code
      )))

At this point I have the error in the _cinint_ of PyGroup2:

Cannot convert Python object argument to type 'Rectangle'

I suppose there is some mistake in my pyx file, but I cannot tell what, since I'm defining there the Rectangle for python.


回答1:


You should use PyRectangle in the signatures of def-functions and PyRectangle.c_rect when passing rectangles to C++-functions.

That means your code should be:

cdef class PyGroup2:
    ...
    def __cinit__(self, PyRectangle rect0, PyRectangle rect1):
        self.c_group2 = Group2(rect0.c_rect, rect1.c_rect)

Read on for a more detailed explanation why.


All arguments passed to def-functions are Python-objects (i.e. of type object in Cython-parlance), after all those functions will be called from pure Python, which only knows Python-objects.

However, you can add some syntactic sugar and use "late-binding" in the signature of a def-function, for example, instead of

def do_something(n):
  ...

use

def do_something(int n):
  ...

Under the hood, Cython will transform this code to something like:

def do_something(n_):
   cdef int n = n_ # conversion to C-int
   ...

This automatic conversion is possible for builtin-types like int or double, because there is functionality in Python-C-API for these conversions (i.e. PyLong_AsLong, PyFloat_AsDouble). Cython also handles the error checking, so you should not undertake these conversion manually.

However, for user-defined types/classes like your Rectangle-class such automatic conversion is not possible - Cython can only automatically convert to cdef-classes/extensions, i.e. PyRectangle, thus PyRectangle should be used in the signature:

cdef class PyGroup2:
    ...
    def __cinit__(self, PyRectangle rect0, PyRectangle rect1):
        ...

After Cython took care of conversion from object to PyRectangle, the last step from PyRectangle to Rectangle must be taken manually by utilizing the c_rect - pointer:

...
def __cinit__(self, PyRectangle rect0, PyRectangle rect1):
    self.c_group2 = Group2(rect0.c_rect, rect1.c_rect)

The rules are similar for cpdef-function, because they can be called from pure Python. The "early binding" works only for types which Cython can automatically coverted from/to Python objects.

Unsurprisingly, the only function which can habe C++-classes in their signatures are the cdef-functions.



来源:https://stackoverflow.com/questions/52262669/cython-example-with-more-than-one-class

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!