2012-04-12 10 views
11

Mam C++ klasy z metody wirtualnej:Czy mogę zastąpić wirtualną funkcję C++ w Pythonie za pomocą Cythona?

//C++ 
class A 
{ 

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

}; 

Co chciałbym zrobić to narazić tę klasę Pythona z Cython, dziedziczą z tej klasy w Pythonie i mieć prawidłowe przesłonięta nazywa:

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

Czy istnieje sposób, aby to zrobić? Teraz myślę, że może być również świetna, jeśli możemy zastąpić wirtualną metodę również w Cython (w pliku pyx), ale pozwalając użytkownikom robić to w czystym Pythonie jest ważniejsze.

Edit: Jeśli to nie pomaga, rozwiązaniem może być użycie Pseudokod podane tutaj: http://docs.cython.org/src/userguide/pyrex_differences.html#cpdef-functions

Ale są dwa problemy, a następnie:

  • nie wiem jak to napisać pseudokod w Cython
  • może jest lepszym rozwiązaniem
+0

Czy próbowałeś swojego kodu? –

+0

tak, oczywiście. Zwraca 2. Czy potrzebujesz również źródła pyx (co jest całkiem błędne, ale nie mogłem go jeszcze znaleźć)? – ascobol

+0

Nie, nie sądzę, że mogę pomóc. Myślę, że boost.python obsługuje to. –

Odpowiedz

8

Doskonale!

Niezupełna, ale wystarczająca. Byłem w stanie zrobić lewę dla mojego własnego celu. Łączenie tego postu ze źródłami powiązanymi powyżej. To nie było łatwe, ponieważ jestem początkującym w Cython, ale potwierdzam, że jest to jedyny sposób, jaki mogłem znaleźć na stronie internetowej.

Wielkie dzięki dla was.

Żałuję, że nie mam tyle czasu wchodzić w szczegóły tekstowych, ale tutaj są moje pliki (może pomóc, aby uzyskać dodatkowy punkt widzenia, w jaki sposób umieścić to wszystko razem)

setup .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: Uwaga na temat PURE metod wirtualnych (wydaje się być dość powracającym problemem). Jak pokazano w powyższym kodzie, w ten szczególny sposób, "TestClass :: override_me()" NIE MOŻE być czyste, ponieważ musi być możliwe wywołanie w przypadku, gdy metoda nie zostanie nadpisana w rozszerzonej klasie Pythona (aka: jeden nie wchodzi w "błąd"/"pominięcie nie znaleziono" w części "ITestClass :: override_me()").

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

wreszcie rozmowy python:

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) 

ten powinien uczynić poprzednią pracę związaną z nadzieją więcej prosto do punktu, my dyskutujemy tutaj ..

EDYCJA: Z drugiej strony powyższy kod mógł zostać zoptymalizowany za pomocą "hasattr" zamiast bloku try/catch:

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 

Powyższy kod ma oczywiście znaczenie tylko w przypadku, gdy nie przesłonimy metody "override_me".

+1

Uwaga dla tych, którzy chcą wypróbować ten przykład: brakuje implementacji TestClass() i ~ TestClass(). Spowoduje to błąd "ImportError: ./elps.so: undefined symbol: _ZTIN4elps9TestClassE ". Po prostu dodaj pustą implementację inline – ascobol

+0

Czy istnieje rozwiązanie, które pozwala ujawnić metody wirtualne (np. Override_me()) po stronie Pythona? – ascobol

+0

Dopóki zmieniasz nazwę, powinieneś być w stanie: 'def call_override_me (self): return self.thisptr.override_me()' ?? –

9

rozwiązaniem jest nieco CO mplicated, ale jest to możliwe. Jest przykładem w pełni pracuje tutaj: https://bitbucket.org/chadrik/cy-cxxfwk/overview

Oto przegląd techniki:

Tworzenie wyspecjalizowanych podklasy class A którego celem będzie współdziałać z rozszerzeniem 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); 
}; 

The konstruktor pobiera obiekt Pythona, który jest przykładem naszego rozszerzenia 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 przedłużenie tej podklasy w Cython, wdrożenie wszystkich metod nie-wirtualnych w standardowy sposób

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

Tworzenie wirtualnych i czystych metod wirtualnych jak public api funkcji, które mają jako argumenty instancji przedłużacza, argumenty metoda, oraz wskaźnik błędu:

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) 

wykorzystania tych funkcji w C++ pośrednią tak:

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

szybko ADAP ted mój kod do twojego przykładu, więc mogą być błędy. Spójrz na pełny przykład w repozytorium, który powinien odpowiedzieć na większość twoich pytań. Możesz go rozwidlić i dodać własne eksperymenty, to jeszcze nie wszystko!

+0

To świetny start, wielkie dzięki. Ale czy metoda override_me() może zostać wywołana przez skrypt Pythona? Jeśli ta metoda nie jest czysto wirtualna w C++, to powinno być możliwe wywołanie jej z części Python – ascobol

Powiązane problemy