Наследование Cython и С++

У меня есть 2 класса, A и B. B наследует от A.

//C++    
class A
{
    public:
        int getA() {return this->a;};
        A() {this->a = 42;}
    private:
        int a;

};

class B: public A
{
    public:
       B() {this->b = 111;};
       int getB() {return this->b;};
    private:
        int b;

};

Теперь я хотел бы связать эти два класса с помощью Cython и иметь возможность вызвать метод getA() из экземпляра B:

a = PyA()
b = PyB()
assert a.getA() == b.getA()

В настоящее время мой файл pyx выглядит так:

cdef extern from "Inherit.h" :
    cdef cppclass A:
       int getA()

    cdef cppclass B(A):
       int getB()


cdef class PyA:
    cdef A* thisptr

    def __cinit__(self):
       print "in A: allocating thisptr"
       self.thisptr = new A()
    def __dealloc__(self):
       if self.thisptr:
           print "in A: deallocating thisptr"
           del self.thisptr

    def getA(self):
       return self.thisptr.getA()

cdef class PyB(PyA):
    def __cinit__(self):
       if self.thisptr:
          print "in B: deallocating old A"
          del self.thisptr
       print "in B: creating new b"
       self.thisptr = new B()

    def __dealloc__(self):
       if self.thisptr:
           print "in B: deallocating thisptr"
           del self.thisptr
           self.thisptr = <A*>0

    def getB(self):
       return (<B*>self.thisptr).getB()

Хотя я надеюсь, что этот код не делает ничего слишком опасного, я также надеюсь, что есть лучший способ справиться с этим.

Также с помощью модуля создается следующий вывод:

>>> from inherit import *
>>> b = PyB()
in A: allocating thisptr
in B: deallocating old A
in B: creating new b
>>> b.getA()
42
>>> b.getB()
111
>>> del b
in B: deallocating thisptr

И мне не очень нравится выделять экземпляр A только для его немедленного освобождения.

Любые советы о том, как сделать это правильно?

Ответ 1

Я делаю некоторые эксперименты и имею вполне готовый ответ, но теперь я вижу, где проблема:

Если тип расширения имеет базовый тип, метод __cinit__базовый тип автоматически вызывается до того, как ваш метод __cinit__называется; вы не можете явно вызвать унаследованный метод __cinit__.

Таким образом, реальная проблема заключается в том, что типы Cython до сих пор не имеют конструкторов, только pre initializer hook __cinit__, которые ведут себя больше как конструкторы по умолчанию. Вы не можете вызывать виртуальный метод из конструктора, и вы не можете вызывать его из __cinit__ либо (если вы совершаете вызов, он ведет себя как не виртуальный).

Как-то внутри __cinit__ type(self) возвращает правильный объект типа, но он бесполезен. Cython не имеет статических полей, методы и тип объекта могут быть только экземпляром type (без метаклассов). Python @staticmethod легко преодолеваем, поэтому он бесполезен.

Таким образом, нет другого способа разместить выделение внутри def __init__(self): и проверить инициализированный thisptr везде, где вы его используете.

Вы можете подумать о создании глобального фиктивного объекта С++ и назначить его thisptr, чтобы избежать проверки и сбоя. Захват инициализатора сообщений отсутствует, поэтому вы не сможете проверить, действительно ли была выполнена правильная инициализация.

Ответ 2

Я никогда раньше не смотрел на Китона, поэтому, пожалуйста, простите меня, если это так. Тем не менее:

В С++ каждый раз, когда вы bar : foo (bar наследуете от foo), если foo имеет конструктор по умолчанию, он будет вызываться автоматически при создании bar.... если вы не вызовите свой собственный конструктор для родителя.

Я не знаю Python, но какой-то быстрый Googling говорит мне, что применяются те же принципы. т.е. конструктор по умолчанию для PyA вызывается только в том случае, если PyB не вызывает вручную другое.

В этом случае не будет что-то вроде этой работы?

cdef class PyA:
    cdef A* thisptr

    def __cinit__(self, bypasskey="")
       if bypasskey == "somesecret"
           print "in A: bypassing allocation"
       else
           print "in A: allocating thisptr"
           self.thisptr = new A()

...

cdef class PyB(PyA):
    def __cinit__(self):
       super( PyB, self ).__init__("somesecret")

Помните, я уверен, что это грубо. Но, может быть, идея здесь даст вам кое-что для работы?


Вот еще одна идея. Я почти уверен, что это сработает (но синтаксис выключен), и он, безусловно, намного чище, чем выше:

cdef class PyA:
    cdef A* thisptr

    def __cinit__(self, t=type(A))
           self.thisptr = new t()

...

cdef class PyB(PyA):
    def __cinit__(self):
       super( PyB, self ).__init__(type(B))

Или, может быть, это будет выглядеть?

cdef class PyA:
    cdef A* thisptr

    def __cinit__(self, t=A)
           self.thisptr = new t()

...

cdef class PyB(PyA):
    def __cinit__(self):
       super( PyB, self ).__init__(B)

Я не отправляю это за щедрость (и вы не обязаны назначать его кому-либо), я просто делюсь с вами некоторыми мыслями.

Я думаю, вы можете/должны быть в состоянии избежать "срыва интерпретатора", если вы либо

a) сделать второй конструктор видимым только для b (не знаю, возможно ли это), или

b) проверьте, является ли значение null до его использования в другом месте, или

c) убедитесь, что вызывающая функция была конструктором для b перед обходом выделения.

Кроме того, документация Cython С++ делает это довольно ясным, что не может быть идиоматических решений для всех адаптаций на С++ с неопределенными, ручными волнами цитаты вроде "Может ли кто-нибудь экспериментировать с другими (вы?), чтобы найти самые элегантные способы решения этой проблемы".

Ответ 3

(Я новичок как для Python, так и для Cython, поэтому возьмите этот ответ за то, что он стоит.) Если вы инициализируете функцию thisptr в функциях __init__, а не __cinit__, все, кажется, работает в этом конкретном примере без дополнительное выделение/удаление... в основном измените ваши функции __cinit__ выше:

def __init__(self):
    print "in A: creating new A"
    self.thisptr = new A()

и

def __init__(self):
    print "in B: creating new B"
    self.thisptr = new B()

соответственно. Тем не менее, я уверен, что это, по крайней мере, теоретически небезопасно (и, вероятно, практически небезопасно), но, возможно, кто-то может прокомментировать точно, насколько опасны...

Например, из введения Cython paper мы знаем, что "__init__ не гарантируется выполнение (например, один может создать подкласс и забыть вызвать конструктор предка)." Я не смог построить тестовый пример, где это происходит, но это, вероятно, связано с общим отсутствием знаний Python с моей стороны...