Пример Cython с более чем одним классом - PullRequest
0 голосов
/ 10 сентября 2018

Я прошу вашей помощи, потому что я пытаюсь запустить пример с Cython немного более сложным, чем пример с одним классом, который можно найти во многих руководствах (например, это руководство ). Я не нашел никакого «более продвинутого» учебника, поэтому я надеюсь, что этот вопрос будет полезен и для людей, которые пытаются изучить его немного глубже.

Я напишу здесь шаги, которые я предпринял, надеясь, что кто-то скажет мне, где моя ошибка.

У меня есть класс Rectangle c ++ (для краткости я поместил здесь только файл .h):

#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

и Group2 класс. Очень простой пример класса, конструктор которого принимает в качестве входных данных 2 прямоугольника:

#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

Затем я создаю grp2.pyx файл с определением Rectangle и 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

Затем я компилирую эти два класса в статической библиотеке c ++ с командной строкой:

gcc -c -fPIC Group2.cpp Rectangle.cpp

и

ar rcs libexample.a Group2.o Rectangle.o

Для завершения я создаю файл cython setup.py , который я вызываю из командной строки:

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

На данный момент у меня есть ошибка в _cinint_ из PyGroup2 :

Невозможно преобразовать аргумент объекта Python в тип 'Rectangle'

Полагаю, в моем файле pyx есть какая-то ошибка, но я не могу сказать, что именно, так как я определяю Rectangle для python.

1 Ответ

0 голосов
/ 11 сентября 2018

Вы должны использовать PyRectangle в сигнатурах def -функций и PyRectangle.c_rect при передаче прямоугольников в C ++ - функции.

Это означает, что ваш код должен быть:

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

Продолжайте читать для более подробного объяснения, почему.


Все аргументы, передаваемые def -функциям, являются объектами Python (то есть типа object на языке Cython), после того как все эти функции будут вызываться из чистого Python, который знает только объекты Python.

Однако вы можете добавить синтаксический сахар и использовать «позднее связывание» в сигнатуре функции def, например, вместо

def do_something(n):
  ...

использование

def do_something(int n):
  ...

Под капотом Cython преобразует этот код в нечто вроде:

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

Это автоматическое преобразование возможно для встроенных типов, таких как int или double, поскольку для этих преобразований имеется функциональность в Python-C-API (т. Е. PyLong_AsLong, PyFloat_AsDouble). Cython также обрабатывает проверку ошибок, поэтому вам не следует предпринимать эти преобразования вручную.

Однако для пользовательских типов / классов, таких как Rectangle -класс, такое автоматическое преобразование невозможно - Cython может автоматически преобразовывать только в cdef -классы / расширения, т. Е. PyRectangle, поэтому PyRectangle следует использоваться в подписи:

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

После того, как Cython позаботился о преобразовании с object в PyRectangle, последний шаг с PyRectangle до Rectangle должен быть выполнен вручную с помощью указателя c_rect -:

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

Правила схожи для cpdef -функции, потому что они могут быть вызваны из чистого Python. «раннее связывание» работает только для типов, которые Cython может автоматически покрывать из / в объекты Python.

Неудивительно, что единственной функцией, которая может содержать C ++ -классы в их сигнатурах, являются cdef -функции.

...