[英]override a C++ virtual function within Python with Boost.python?
[英]Can I override a C++ virtual function within Python with Cython?
我有一個帶有虛方法的C ++類:
//C++
class A
{
public:
A() {};
virtual int override_me(int a) {return 2*a;};
int calculate(int a) { return this->override_me(a) ;}
};
我想要做的是使用Cython將此類暴露給Python,從Python繼承此類並具有正確的重寫:
#python:
class B(PyA):
def override_me(self, a):
return 5*a
b = B()
b.calculate(1) # should return 5 instead of 2
有沒有辦法做到這一點 ? 現在我想,如果我們可以在Cython中覆蓋虛擬方法(在pyx文件中)也可能很棒,但允許用戶在純python中執行此操作更為重要。
編輯 :如果這有幫助,解決方案可能是使用此處給出的偽代碼: http : //docs.cython.org/src/userguide/pyrex_differences.html#cpdef-functions
但是有兩個問題:
解決方案有點復雜,但有可能。 這里有一個完整的例子: https : //bitbucket.org/chadrik/cy-cxxfwk/overview
以下是該技術的概述:
創建一個class A
類的專用子類,其目的是與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);
};
構造函數接受一個python對象,它是我們的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);
}
在cython中創建此子類的擴展,以標准方式實現所有非虛方法
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()
創建虛擬和純虛方法作為public api
函數,將擴展實例,方法參數和錯誤指針作為參數:
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)
在你的c ++中間函數中使用這些函數,如下所示:
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;
}
我很快就將我的代碼改編為你的例子,所以可能會有錯誤。 看一下存儲庫中的完整示例,它應該回答您的大部分問題。 隨意分叉並添加您自己的實驗,它遠未完成!
優秀的 !
不完整但足夠。 為了我自己的目的,我已經能夠做到這一點。 將此帖與上面鏈接的來源相結合。 這並不容易,因為我是Cython的初學者,但我確認這是我能通過www找到的唯一方式。
非常感謝你們。
對不起,我沒有那么多時間進入文本細節,但這里有我的文件(可能有助於獲得關於如何將所有這些放在一起的額外觀點)
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:關於PURE虛擬方法的說明(它似乎是一個非常經常出現的問題)。 如上面的代碼所示,以這種特殊的方式,“TestClass :: override_me()”不能是純粹的,因為它必須是可調用的,以防方法在Python的擴展類中沒有被覆蓋(又名:一個不屬於“ITestClass :: override_me()”主體的“錯誤”/“覆蓋未找到”部分。
擴展名: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()
最后,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)
這應該使以前的鏈接工作更加直接到我們在這里討論的點......
編輯:另一方面,上面的代碼可以通過使用'hasattr'而不是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
當然,上面的代碼僅在我們不覆蓋'override_me'方法的情況下才有所不同。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.