Frage Cython- und C ++ - Vererbung


Ich habe 2 Klassen, A und B. B erbt von 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;

};

Nun möchte ich diese beiden Klassen mit Cython verbinden und die Methode getA () von einer B-Instanz aufrufen:

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

Momentan sieht meine Pyx-Datei so aus:

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

Während ich hoffe, dass dieser Code nichts zu gefährlich macht, hoffe ich auch, dass es einen besseren Weg gibt, damit umzugehen.

Auch die Verwendung des Moduls erzeugt folgende Ausgabe:

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

Und ich mag es nicht wirklich, eine A-Instanz zuzuweisen, nur um sie sofort danach freizugeben.

Irgendwelche Ratschläge, wie man es richtig macht?


11
2018-04-24 13:00


Ursprung


Antworten:


Ich mache ein paar Experimente und habe schon eine Antwort, aber jetzt sehe ich, wo das Problem ist:

Wenn Ihr Erweiterungstyp einen Basistyp hat, wird der __cinit__ Methode der   Der Basistyp wird automatisch vor dem aufgerufen __cinit__ Methode ist   namens; Sie können das Vererbte nicht explizit aufrufen __cinit__ Methode.

Das eigentliche Problem besteht also darin, dass Cython-Typen immer noch keine Konstruktoren haben, sondern nur Vorinitialisierungs-Hooks __cinit__ die sich mehr wie Standardkonstruktoren verhalten. Sie können die virtuelle Methode nicht vom Konstruktor aufrufen, und Sie können sie nicht aufrufen __cinit__ entweder (wenn Sie einen Anruf tätigen, verhält es sich wie nicht virtuell).

Irgendwie drinnen __cinit__ das type(self) return correct type object, aber es ist nutzlos. Cython haben keine statischen Felder, Methoden und Typ-Objekt kann nur eine Instanz von sein type (keine Metaklassen). Python @staticmethod ist leicht überschreibbar, also ist es nutzlos.

Es gibt also keinen anderen Weg, die Zuteilung in den Raum zu stellen def __init__(self):, und prüfen Sie auf initialisiert thisptr wo auch immer Sie es verwenden.

Sie können ein globales C ++ - Dummy-Objekt erstellen und es zuweisen thisptr um zu vermeiden, zu überprüfen und abzustürzen. Es gibt keinen Post-Initialisierer-Hook, so dass Sie nicht überprüfen können, ob bereits eine korrekte Initialisierung stattgefunden hat.


7
2018-05-09 23:34



Ich habe Cython noch nie zuvor gesehen, also bitte vergib mir, wenn es weit weg ist. Das gesagt:

In C ++, wann immer Sie haben bar : foo (bar erben von foo), ob foo Hat einen Standardkonstruktor, wird er automatisch aufgerufen, wenn bar wird erstellt .... außer Sie rufen Ihren eigenen benutzerdefinierten Konstruktor für das übergeordnete Element auf.

Ich kenne Python nicht, aber ein kurzes Googeln sagt mir, dass die gleichen Prinzipien gelten. d.h. der Standardkonstruktor für PyA wird nur dann aufgerufen, wenn PyB ruft einen anderen nicht manuell an.

Würde so etwas in diesem Fall nicht funktionieren?

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

Wohlgemerkt, ich bin mir sicher, dass es grob ist. Aber vielleicht gibt dir die Idee hier etwas, mit dem du arbeiten kannst?


Hier ist eine andere Idee. Ich bin mir fast sicher, dass es funktionieren wird (aber die Syntax ist ausgeschaltet), und es ist sicherlich viel sauberer als das obige:

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

Oder sieht es vielleicht so aus?

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)

Ich poste das nicht für die Bounty (und du bist nicht gezwungen, es jemandem zuzuteilen), ich teile nur ein paar Gedanken mit dir.

Ich denke, Sie können / sollten in der Lage sein, "den Dolmetscher zu stürzen", wenn Sie das auch können

a) mache den zweiten Konstruktor nur für b sichtbar (weiß nicht, ob das möglich ist), oder

b) überprüfe, ob a null ist, bevor du es an anderer Stelle verwendest, oder

c) prüfe, dass die aufrufende Funktion der Konstruktor für b war, bevor die Zuweisung umgangen wurde.

Auch die Cython C ++ Dokumentation macht es ziemlich klar dass es keine idiomatischen Lösungen für alle C ++ - Anpassungen geben kann, mit vagen, hand-welligen Zitaten wie "Es könnte einige Experimente von anderen erfordern (Sie?), um die elegantesten Wege zu finden, dieses Problem zu lösen."


3
2018-05-09 16:18



(Ich bin sowohl mit Python als auch mit Cython neu, also nehmen Sie diese Antwort für das, was es wert ist.) Wenn Sie das theptr in initialisieren __init__ Funktionen statt __cinit__ Funktionen, Dinge scheinen in diesem speziellen Beispiel ohne die zusätzliche Zuweisung / Löschung zu funktionieren ... im Grunde ändern Sie Ihre __cinit__ funktioniert oben zu:

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

Und

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

beziehungsweise. Ich bin mir aber sicher, dass dies zumindest theoretisch unsicher ist (und wahrscheinlich auch praktisch unsicher ist), aber vielleicht könnte jemand genau sagen, wie unsicher ...

Zum Beispiel von der Cython-Einführung Papier- Wir wissen das "__init__ Es ist nicht garantiert, dass es ausgeführt wird (zum Beispiel könnte man eine Unterklasse erstellen und vergessen, den Vorgängerkonstruktor aufzurufen). "Ich konnte keinen Testfall erstellen, wo dies auftritt, aber dies ist wahrscheinlich auf einen allgemeinen Mangel zurückzuführen von Python Wissen meinerseits ...


1
2018-05-23 20:59