2012-04-24 14 views
11

tengo 2 clases, A y B. B hereda deCython y C++ herencia

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

}; 

Ahora me gustaría para interconectar estas dos clases usando Cython y tener la posibilidad de llamar a la Geta() método A. desde una instancia de B:

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

Actualmente mi archivo pyx se parece a esto:

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

Mientras espero que este código es n Al hacer algo demasiado peligroso, también espero que haya una mejor manera de manejarlo.

También usando el módulo genera el siguiente resultado:

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

y no me gusta mucho la asignación de un Un ejemplo sólo para liberarla inmediatamente después.

¿Algún consejo sobre cómo hacerlo correctamente?

Respuesta

7

que hacer algunos experimentos, y tienen respuesta bastante listo, pero ahora veo dónde está el problema:

Si el tipo de extensión tiene un tipo base, el método __cinit__ del tipo de base que se llama automáticamente antes su método __cinit__ es llamado; no puede llamar explícitamente al método heredado __cinit__.

Así que el verdadero problema es que los tipos Cython todavía no tienen constructores, sólo pre gancho inicializador __cinit__ que se comportan más como constructores por defecto. No puede llamar al método virtual desde el constructor y tampoco puede llamarlo desde el __cinit__ (si hace una llamada, se comporta como no virtual).

De alguna manera dentro de __cinit__ el type(self) devuelve el tipo de objeto correcto, pero es inútil. Cython no tiene campos estáticos, los métodos y el tipo de objeto pueden ser solo instancia de type (sin metaclases). Python @staticmethod es fácil de sobrepasar, por lo que es inútil.

Así que no hay otra manera de poner la asignación dentro de def __init__(self):, y verifique si hay thisptr inicializado donde quiera que lo use.

Puede considerar la creación de un objeto C++ ficticio global y asignarlo al thisptr para evitar la comprobación y el bloqueo. No hay enganche de inicialización posterior, por lo que no podrá verificar si la inicialización correcta ya se ha realizado.

3

Nunca antes he visto a Cython, así que por favor, perdónenme si esto está fuera de lugar. Dicho esto:

En C++, cada vez que tenga bar : foo (bar heredando de foo), si foo tiene un constructor por defecto, que se llamará automáticamente cuando se crea bar .... a menos que llame su propio constructor personalizado para el padre.

No sé Python, pero Google me dice rápidamente que se aplican los mismos principios. es decir, el constructor predeterminado para PyA solo se invocará si PyB no llama manualmente a otro.

En ese caso, ¿no funcionaría algo como esto?

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

Mente, estoy seguro de que es crudo. Pero tal vez la idea aquí le dará algo con qué trabajar?


Aquí hay otra idea. Estoy casi seguro de que va a trabajar (pero que la sintaxis está apagado), y es sin duda mucho más limpio que el anterior:

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

O tal vez parecerá que esto?

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) 

No estoy publicar esto para la generosidad (y usted no está obligado a asignar a nadie), sólo estoy compartiendo con ustedes algunas ideas.

Creo que se puede/debe ser capaz de evitar "ataques al intérprete" si bien

a) hacer que el segundo constructor accesibles solamente a b (no sé si esto es posible), o

b) compruebe si a es nulo antes de usarlo en otro lugar, o

c) compruebe que la función de llamada era el constructor de b antes de omitir la asignación.

También, la documentación de Cython C++ makes it rather clear que puede no haber soluciones idiomáticas para todas las adaptaciones de C++, con citas vagas, onduladas a mano como "Puede llevar algo de experimentación por otros (¿usted?) Encontrar las formas más elegantes de manejando este problema ".

+0

Bueno, abriendo una recompensa, estaba buscando una construcción idiomática para este caso. Usted dice que no conoce Python y Cython. Si bien su respuesta puede adaptarse para ser el código legal de Python (y Cython), esto le daría al usuario de Python el poder de bloquear el intérprete, lo cual es en mi opinión mucho peor que perder asignaciones de memoria – ascobol

+0

Mi respuesta es demasiado larga para un comentario, por favor vea la última mitad de mi publicación –

+0

a) Creo que solo puede haber un constructor en Cython, como en Python. b) luego en cada método deberíamos verificar si la inicialización es correcta ... c) tal vez esto sea posible con un valor arbitrario predefinido en el módulo C. En PyA __ cinit__ verificaríamos un argumento adicional con este valor no trivial que omitiría la asignación. En este caso, un usuario no puede bloquear el intérprete "por accidente". – ascobol

1

(soy nuevo en tanto Python y Cython, por lo que aprovecho esta respuesta para lo que vale la pena.) Si inicializa la thisptr en __init__ funciones en lugar de __cinit__ funciones, las cosas parecen funcionar en este ejemplo particular sin la asignación adicional/deleción ... básicamente cambiar sus funciones __cinit__ arriba a:

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

Y

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

respectivamente. Sin embargo, estoy seguro de que esto es al menos teóricamente inseguro (y probablemente la práctica insegura así), pero tal vez alguien podría comentar sobre exactamente cómo inseguro ...

Por ejemplo, a partir de la introducción Cython paper sabemos que "es __init__ no se garantiza que se ejecute (por ejemplo, uno podría crear una subclase y olvidarse de llamar al constructor ancestro)."No he podido construir un caso de prueba donde esto ocurre, pero esto probablemente se deba a una falta general de conocimiento de Python por mi parte ...