2012-04-12 13 views
11

que tienen una clase de C++ con un método virtual:¿Puedo anular una función virtual de C++ dentro de Python con Cython?

//C++ 
class A 
{ 

    public: 
     A() {}; 
     virtual int override_me(int a) {return 2*a;}; 
     int calculate(int a) { return this->override_me(a) ;} 

}; 

Lo que me gustaría hacer es exponer esta clase de Python con Cython, heredar de esta clase en Python y tener la correcta anulado llamada:

#python: 
class B(PyA): 
    def override_me(self, a): 
     return 5*a 
b = B() 
b.calculate(1) # should return 5 instead of 2 

¿Hay alguna manera de hacerlo? Ahora estoy pensando, también podría ser genial si pudiéramos anular el método virtual en Cython también (en un archivo pyx), pero permitir que los usuarios hagan esto en python puro es más importante.

Editar: Si esto ayuda, una solución podría ser utilizar el pseudocódigo se da aquí: http://docs.cython.org/src/userguide/pyrex_differences.html#cpdef-functions

Pero hay dos problemas a continuación:

  • No sé cómo escribir esto pseudocódigo en Cython
  • tal vez hay un mejor enfoque
+0

¿Has probado tu código? –

+0

sí, por supuesto. Devuelve 2. ¿Necesita también la fuente de pyx (lo cual es completamente incorrecto, pero no pude encontrar una solución aún)? – ascobol

+0

No, no creo que pueda ayudar. Creo que boost.python lo admite. –

Respuesta

8

¡Excelente!

No completo pero suficiente. He podido hacer el truco para mi propio propósito. Combinando esta publicación con las fuentes vinculadas anteriormente. No ha sido fácil, dado que soy un principiante en Cython, pero confirmo que es la única forma que puedo encontrar en el www.

Muchas gracias chicos.

lo siento que no tengo tanto tiempo entrar en detalles textuales, pero aquí están mis archivos (que podría ayudar a conseguir un punto de vista adicional sobre cómo poner todo esto junto)

instalación .py:

from distutils.core import setup 
from distutils.extension import Extension 
from Cython.Distutils import build_ext 

setup(
    cmdclass = {'build_ext': build_ext}, 
    ext_modules = [ 
    Extension("elps", 
       sources=["elps.pyx", "src/ITestClass.cpp"], 
       libraries=["elp"], 
       language="c++", 
      ) 
    ] 
) 

TestClass:

#ifndef TESTCLASS_H_ 
#define TESTCLASS_H_ 


namespace elps { 

class TestClass { 

public: 
    TestClass(){}; 
    virtual ~TestClass(){}; 

    int getA() { return this->a; }; 
    virtual int override_me() { return 2; }; 
    int calculate(int a) { return a * this->override_me(); } 

private: 
    int a; 

}; 

} /* namespace elps */ 
#endif /* TESTCLASS_H_ */ 

ITestClass.h:

#ifndef ITESTCLASS_H_ 
#define ITESTCLASS_H_ 

// Created by Cython when providing 'public api' keywords 
#include "../elps_api.h" 

#include "../../inc/TestClass.h" 

namespace elps { 

class ITestClass : public TestClass { 
public: 
    PyObject *m_obj; 

    ITestClass(PyObject *obj); 
    virtual ~ITestClass(); 
    virtual int override_me(); 
}; 

} /* namespace elps */ 
#endif /* ITESTCLASS_H_ */ 

ITestClass.cpp:

#include "ITestClass.h" 

namespace elps { 

ITestClass::ITestClass(PyObject *obj): m_obj(obj) { 
    // Provided by "elps_api.h" 
    if (import_elps()) { 
    } else { 
     Py_XINCREF(this->m_obj); 
    } 
} 

ITestClass::~ITestClass() { 
    Py_XDECREF(this->m_obj); 
} 

int ITestClass::override_me() 
{ 
    if (this->m_obj) { 
     int error; 
     // Call a virtual overload, if it exists 
     int result = cy_call_func(this->m_obj, (char*)"override_me", &error); 
     if (error) 
      // Call parent method 
      result = TestClass::override_me(); 
     return result; 
    } 
    // Throw error ? 
    return 0; 
} 

} /* namespace elps */ 

Edit2: Una nota acerca de los métodos virtuales puros (que parece ser una preocupación bastante recurrente). Como se muestra en el código anterior, de esa manera particular, "TestClass :: override_me()" NO PUEDE ser puro, ya que tiene que ser invocable en caso de que el método no se anule en la clase extendida de Python (también conocido como: uno no se incluye la parte "error"/"anulación no encontrada" del cuerpo "ITestClass :: override_me()").

Extensión: elps.pyx:

cimport cpython.ref as cpy_ref 

cdef extern from "src/ITestClass.h" namespace "elps" : 
    cdef cppclass ITestClass: 
     ITestClass(cpy_ref.PyObject *obj) 
     int getA() 
     int override_me() 
     int calculate(int a) 

cdef class PyTestClass: 
    cdef ITestClass* thisptr 

    def __cinit__(self): 
     ##print "in TestClass: allocating thisptr" 
     self.thisptr = new ITestClass(<cpy_ref.PyObject*>self) 
    def __dealloc__(self): 
     if self.thisptr: 
      ##print "in TestClass: deallocating thisptr" 
      del self.thisptr 

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

# def override_me(self): 
#  return self.thisptr.override_me() 

    cpdef int calculate(self, int a): 
     return self.thisptr.calculate(a) ; 


cdef public api int cy_call_func(object self, char* method, int *error): 
    try: 
     func = getattr(self, method); 
    except AttributeError: 
     error[0] = 1 
    else: 
     error[0] = 0 
     return func() 

Por último, el pitón llamadas:

from elps import PyTestClass as TC; 

a = TC(); 
print a.calculate(1); 

class B(TC): 
# pass 
    def override_me(self): 
     return 5 

b = B() 
print b.calculate(1) 

Esto debería hacer que el trabajo previo relacionado con suerte más recta hasta el punto que estamos discutiendo aquí ..

EDITAR: Por otro lado, el código anterior podría optimizarse utilizando 'hasattr' en lugar de try/catch block:

cdef public api int cy_call_func_int_fast(object self, char* method, bint *error): 
    if (hasattr(self, method)): 
     error[0] = 0 
     return getattr(self, method)(); 
    else: 
     error[0] = 1 

El código anterior, por supuesto, hace la diferencia solo en el caso de que no anulemos el método 'override_me'.

+1

. Solo una nota para aquellos que quieran probar este ejemplo: falta la implementación de TestClass() y ~ TestClass(). Esto causará un error como "ImportError: ./elps.so: undefined symbol: _ZTIN4elps9TestClassE ". Solo agregue una implementación vacía en línea – ascobol

+0

¿Existe, con su solución, una forma de exponer los métodos virtuales (es decir, override_me()) al lado de Python? – ascobol

+0

Mientras cambie el nombre, debería ser capaz de: 'def call_override_me (self): return self.thisptr.override_me()' ?? –

9

La solución es algo co complicado, pero es posible. Hay un ejemplo completamente de trabajo aquí: https://bitbucket.org/chadrik/cy-cxxfwk/overview

He aquí un resumen de la técnica:

crear una subclase especializado de class A cuya finalidad será la de interactuar con una extensión Cython:

// created by cython when providing 'public api' keywords: 
#include "mycymodule_api.h" 

class CyABase : public A 
{ 
public: 
    PyObject *m_obj; 

    CyABase(PyObject *obj); 
    virtual ~CyABase(); 
    virtual int override_me(int a); 
}; 

El constructor toma un objeto python, que es la instancia de nuestra extensión cython:

CyABase::CyABase(PyObject *obj) : 
    m_obj(obj) 
{ 
    // provided by "mycymodule_api.h" 
    if (import_mycymodule()) { 
    } else { 
    Py_XINCREF(this->m_obj); 
    } 
} 

CyABase::~CyABase() 
{ 
    Py_XDECREF(this->m_obj); 
} 

Creat e una extensión de esta subclase en Cython, la aplicación de todos los métodos no-virtuales en el modo estándar

cdef class A: 
    cdef CyABase* thisptr 
    def __init__(self): 
     self.thisptr = new CyABase(
      <cpy_ref.PyObject*>self) 

    #------- non-virutal methods -------- 
    def calculate(self): 
     return self.thisptr.calculate() 

Crear métodos virtuales virtuales y puro como public api funciones, que toman como argumentos la instancia de extensión, los argumentos del método, y una puntero de error:

cdef public api int cy_call_override_me(object self, int a, int *error): 
    try: 
     func = self.override_me 
    except AttributeError: 
     error[0] = 1 
     # not sure what to do about return value here... 
    else: 
     error[0] = 0 
     return func(a) 

utilizar estas funciones en su C++ intermedia como esto:

int 
CyABase::override_me(int a) 
{ 
    if (this->m_obj) { 
    int error; 
    // call a virtual overload, if it exists 
    int result = cy_call_override_me(this->m_obj, a, &error); 
    if (error) 
     // call parent method 
     result = A::override_me(i); 
    return result; 
    } 
    // throw error? 
    return 0; 
} 

que rápidamente ADAP conecté mi código a tu ejemplo, por lo que podría haber errores. Eche un vistazo al ejemplo completo en el repositorio y debería responder la mayoría de sus preguntas. Siéntase libre de bifurcarlo y agregar sus propios experimentos, ¡está lejos de estar completo!

+0

Este es un gran comienzo, muchas gracias. ¿Pero puede el método override_me() ser llamado por un script de Python? Si este método no es puramente virtual en C++, entonces uno debería poder llamarlo desde la parte de Python – ascobol

Cuestiones relacionadas