14

仮想メソッドを持つ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

しかし、2つの問題があります:

  • Cythonでこの擬似コードを書く方法がわかりません
  • たぶんもっと良いアプローチがあります
4

2 に答える 2

11

解決策はやや複雑ですが、それは可能です。ここに完全に機能する例があります:https ://bitbucket.org/chadrik/cy-cxxfwk/overview

テクニックの概要は次のとおりです。

class ACython拡張機能と対話することを目的とする特殊なサブクラスを作成します。

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

コンストラクターは、cython拡張機能のインスタンスであるpythonオブジェクトを受け取ります。

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

私はすぐに私のコードをあなたの例に適合させたので、間違いがあるかもしれません。リポジトリ内の完全な例を見てください。ほとんどの質問に答えるはずです。自由にフォークして独自の実験を追加してください。完全にはほど遠いです。

于 2012-07-21T21:22:43.870 に答える
9

素晴らしい !

完全ではありませんが、十分です。私は自分の目的のためにトリックをすることができました。この投稿を上記のリンク先のソースと組み合わせます。私は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の拡張クラスでメソッドがオーバーライドされない場合に呼び出す必要があるため、純粋にすることはできません(別名:1つは該当しません) 「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)

これにより、以前のリンクされた作業が、ここで説明しているポイントにもっとまっすぐになることを願っています...

編集:一方、上記のコードは、try /catchブロックの代わりに'hasattr'を使用して最適化できます:

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」メソッドをオーバーライドしない場合にのみ違いがあります。

于 2012-10-02T23:57:49.563 に答える