Наследование 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 с моей стороны...