2012-04-12 41 views
11

Tôi có một lớp C++ với một phương pháp ảo:Tôi có thể ghi đè chức năng ảo C++ trong Python bằng Cython không?

//C++ 
class A 
{ 

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

}; 

Những gì tôi muốn làm là để lộ lớp này để Python với Cython, kế thừa từ lớp này bằng Python và có đúng ghi đè gọi:

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

Có cách nào để thực hiện việc này không? Bây giờ tôi nghĩ, nó cũng có thể tuyệt vời nếu chúng ta có thể ghi đè lên phương pháp ảo trong Cython (trong một tệp pyx), nhưng cho phép người dùng làm điều này trong python tinh khiết là quan trọng hơn.

Sửa: Nếu điều này giúp, một giải pháp có thể được sử dụng giả đưa ra ở đây: http://docs.cython.org/src/userguide/pyrex_differences.html#cpdef-functions

Nhưng có hai vấn đề sau đó:

  • Tôi không biết làm thế nào để viết những dòng này mã giả trong Cython
  • có thể có cách tiếp cận tốt hơn
+0

Bạn đã thử mã của mình chưa? –

+0

có tất nhiên. Nó trả về 2. Bạn có cần nguồn pyx không (đó là đồng bằng sai nhưng tôi không thể tìm thấy một sửa chữa cho nó chưa)? – ascobol

+0

Không, tôi không nghĩ tôi có thể giúp. Tôi nghĩ rằng boost.python hỗ trợ điều này. –

Trả lời

8

Tuyệt vời!

Không đầy đủ nhưng đầy đủ. Tôi đã có thể thực hiện thủ thuật cho mục đích của riêng mình. Kết hợp bài đăng này với các nguồn được liên kết ở trên. Nó không phải là dễ dàng, vì tôi là người mới bắt đầu tại Cython, nhưng tôi xác nhận rằng đó là cách duy nhất tôi có thể tìm thấy trên www.

Cảm ơn bạn rất nhiều.

Tôi xin lỗi mà tôi không có quá nhiều thời gian đi sâu vào chi tiết văn bản, nhưng đây là tác phẩm của tôi (có thể giúp đỡ để có được một điểm bổ sung quan điểm về cách đặt tất cả những điều này với nhau)

thiết lập 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: Một lưu ý về phương pháp ảo PURE (nó dường như là một mối quan tâm khá thường xuyên). Như được hiển thị trong đoạn mã trên, trong thời trang cụ thể đó, "TestClass :: override_me()" KHÔNG THỂ thuần khiết vì nó phải được gọi trong trường hợp phương thức này không được ghi đè trong lớp mở rộng của Python (aka: một không rơi vào phần "lỗi"/"ghi đè không tìm thấy" của nội dung "ITestClass :: override_me()").

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

Cuối cùng, các cuộc gọi 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) 

này nên làm công việc liên quan trước hy vọng hơn thẳng vào vấn đề chúng ta đang thảo luận ở đây ..

EDIT: Mặt khác, mã trên có thể được tối ưu hóa bằng cách sử dụng 'hasattr' thay vì khối 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 

Mã trên, tất nhiên, chỉ tạo sự khác biệt trong trường hợp chúng tôi không ghi đè phương thức 'ghi đè lên'.

+1

Chỉ cần một lưu ý cho những ai muốn thử ví dụ này: việc thi hành TestClass() và ~ TestClass() bị thiếu. Điều này sẽ gây ra lỗi như "ImportError: ./elps.so: biểu tượng không xác định: _ZTIN4elps9TestClassE ". Chỉ cần thêm một thực hiện nội tuyến rỗng – ascobol

+0

Có, với giải pháp của bạn, một cách để lộ phương pháp ảo (tức là override_me()) cho phía Python? – ascobol

+0

Miễn là bạn thay đổi tên, bạn sẽ có thể: 'def call_override_me (self): return self.thisptr.override_me()' ?? –

9

Giải pháp này hơi đồng mplicated, nhưng nó là có thể. Có một ví dụ làm việc đầy đủ ở đây: https://bitbucket.org/chadrik/cy-cxxfwk/overview

Dưới đây là một tổng quan về kỹ thuật này:

Tạo một phân lớp chuyên ngành của class A mà mục đích sẽ tương tác với một phần mở rộng 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); 
}; 

Các constructor có một đối tượng python, đó là thể hiện của phần mở rộng cython của chúng tôi:

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

Tạo e một phần mở rộng của lớp con này trong cython, thực hiện tất cả các phương pháp không ảo theo kiểu tiêu chuẩn

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

Tạo phương pháp ảo ảo và tinh khiết như public api chức năng, mà phải mất như các đối số trường hợp mở rộng, các đối số phương pháp, và một con trỏ lỗi:

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) 

Sử dụng các hàm trong C++ của bạn trung gian như thế này:

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

tôi nhanh chóng ADAP ted mã của tôi để ví dụ của bạn, do đó, có thể có những sai lầm. Hãy xem ví dụ đầy đủ trong kho lưu trữ và nó sẽ trả lời hầu hết các câu hỏi của bạn. Cảm thấy tự do để ngã ba nó và thêm các thí nghiệm của riêng bạn, nó xa hoàn thành!

+0

Đây là một khởi đầu tuyệt vời, cảm ơn rất nhiều. Nhưng phương thức override_me() có thể được gọi bởi một tập lệnh Python không? Nếu phương pháp này không phải là thuần ảo trong C++ thì người ta có thể gọi nó từ phần Python – ascobol

Các vấn đề liên quan