web-dev-qa-db-ja.com

C ++で呼び出されるようにPythonでC ++クラスを実装するにはどうすればよいですか?

私はC++で書かれたクラスインターフェースを持っています。このインターフェイスを実装するクラスもいくつかあり、C++でも記述されています。これらは、本質的に「メイン」を実装する、より大きなC++プログラムのコンテキストで呼び出されます。このインターフェイスの実装をPythonで記述し、C++で記述されたかのように、より大きなC++プログラムのコンテキストで使用できるようにしたいと考えています。

pythonとC++のインターフェースについてはたくさん書かれていますが、私が望むことを行う方法を完全に理解することはできません。私が見つけることができる最も近いものはここにあります: http:// www。 cs.brown.edu/~jwicks/boost/libs/python/doc/tutorial/doc/html/python/exposed.html#python.class_virtual_functions ですが、これは正しくありません。

より具体的には、次のように定義された既存のC++インターフェイスがあるとします。

// myif.h
class myif {
   public:
     virtual float myfunc(float a);
};

私がやりたいことは次のようなものです。

// mycl.py
... some magic python stuff ...
class MyCl(myif):
  def myfunc(a):
    return a*2

次に、C++コードに戻って、次のようなことを言いたいと思います。

// mymain.cc
void main(...) {
  ... some magic c++ stuff ...
  myif c = MyCl();  // get the python class
  cout << c.myfunc(5) << endl;  // should print 10
}

これが十分に明確であることを願っています;)

38
hal3

この答えには2つの部分があります。まず、Pythonで、Python実装がその一部を自由にオーバーライドできるように、インターフェイスを公開する必要があります。次に、C++プログラムを表示する必要があります。 (mainでPythonを呼び出す方法。


既存のインターフェースをPythonに公開する:

最初の部分はSWIGで非常に簡単に実行できます。いくつかの問題を修正するためにサンプルシナリオを少し変更し、テスト用の機能を追加しました。

_// myif.h
class myif {
   public:
     virtual float myfunc(float a) = 0;
};

inline void runCode(myif *inst) {
  std::cout << inst->myfunc(5) << std::endl;
}
_

今のところ、アプリケーションにPythonを埋め込まずに問題を見ていきます。つまり、C++のint main()ではなくPythonでエクセレンスを開始します。これを追加するのはかなり簡単です。後でしかし。

最初に取得することです 言語間ポリモーフィズムが機能しています

_%module(directors="1") module

// We need to include myif.h in the SWIG generated C++ file
%{
#include <iostream>
#include "myif.h"
%}

// Enable cross-language polymorphism in the SWIG wrapper. 
// It's pretty slow so not enable by default
%feature("director") myif;

// Tell swig to wrap everything in myif.h
%include "myif.h"
_

これを行うために、SWIGのディレクター機能をグローバルに、特にインターフェイスに対して有効にしました。残りはかなり標準的なSWIGです。

私はテストを書きましたPython実装:

_import module

class MyCl(module.myif):
  def __init__(self):
    module.myif.__init__(self)
  def myfunc(self,a):
    return a*2.0

cl = MyCl()

print cl.myfunc(100.0)

module.runCode(cl)
_

それで私はこれをコンパイルして実行することができました:

 swig -python -c ++ -Wall myif.i 
 g ++ -Wall -Wextra -shared -o _module.so myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7 
 
 python mycl.py 
 200.0 
 10 

まさにそのテストからあなたが見たいと思うもの。


アプリケーションにPythonを埋め込む:

次に、mymain.ccの実際のバージョンを実装する必要があります。私はそれがどのように見えるかについてのスケッチをまとめました:

_#include <iostream>
#include "myif.h"
#include <Python.h>

int main()
{
  Py_Initialize();

  const double input = 5.0;

  PyObject *main = PyImport_AddModule("__main__");
  PyObject *dict = PyModule_GetDict(main);
  PySys_SetPath(".");
  PyObject *module = PyImport_Import(PyString_FromString("mycl"));
  PyModule_AddObject(main, "mycl", module);

  PyObject *instance = PyRun_String("mycl.MyCl()", Py_eval_input, dict, dict);
  PyObject *result = PyObject_CallMethod(instance, "myfunc", (char *)"(O)" ,PyFloat_FromDouble(input));

  PyObject *error = PyErr_Occurred();
  if (error) {
    std::cerr << "Error occured in PyRun_String" << std::endl;
    PyErr_Print();
  }

  double ret = PyFloat_AsDouble(result);
  std::cout << ret << std::endl;

  Py_Finalize();
  return 0;
}
_

これは基本的には単なる標準です 埋め込みPython別のアプリケーション 。これは機能し、あなたが見たいと思うものを正確に提供します:

 g ++ -Wall -Wextra -I/usr/include/python2.7 main.cc -o main -lpython2.7 
 ./ main 
 200.0 
 10 
 10 

パズルの最後のピースは、Pythonでインスタンスを作成して得た_PyObject*_を_myif *_に変換できることです。SWIGでもこれはかなり簡単です。

まず、SWIGにランタイムをヘッダーファイルで公開するように依頼する必要があります。これは、SWIGへの追加の呼び出しで行います。

 swig -Wall -c ++ -python -external-runtime runtime.h 

次に、SWIGモジュールを再コンパイルし、SWIGが名前について認識しているタイプのテーブルを明示的に指定して、main.cc内から検索できるようにする必要があります。以下を使用して.soを再コンパイルします。

 g ++ -DSWIG_TYPE_TABLE = myif -Wall -Wextra -shared -o _module.so myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7 

次に、main.ccに_PyObject*_を_myif*_に変換するためのヘルパー関数を追加します。

_#include "runtime.h"
// runtime.h was generated by SWIG for us with the second call we made

myif *python2interface(PyObject *obj) {
  void *argp1 = 0;
  swig_type_info * pTypeInfo = SWIG_TypeQuery("myif *");

  const int res = SWIG_ConvertPtr(obj, &argp1,pTypeInfo, 0);
  if (!SWIG_IsOK(res)) {
    abort();
  }
  return reinterpret_cast<myif*>(argp1);
}
_

これで、main()内から使用できるようになりました。

_int main()
{
  Py_Initialize();

  const double input = 5.5;

  PySys_SetPath(".");
  PyObject *module = PyImport_ImportModule("mycl");

  PyObject *cls = PyObject_GetAttrString(module, "MyCl");
  PyObject *instance = PyObject_CallFunctionObjArgs(cls, NULL);

  myif *inst = python2interface(instance);
  std::cout << inst->myfunc(input) << std::endl;

  Py_XDECREF(instance);
  Py_XDECREF(cls);

  Py_Finalize();
  return 0;
}
_

最後に、main.ccを_-DSWIG_TYPE_TABLE=myif_でコンパイルする必要があります。これにより、次のようになります。

 ./ main 
 11 
39
Flexo

最小限の例; Baseが純粋仮想ではないという事実によって複雑になることに注意してください。そこに行きます:

  1. baz.cpp:

    _#include<string>
    #include<boost/python.hpp>
    using std::string;
    namespace py=boost::python;
    
    struct Base{
      virtual string foo() const { return "Base.foo"; }
      // fooBase is non-virtual, calling it from anywhere (c++ or python)
      // will go through c++ dispatch
      string fooBase() const { return foo(); }
    };
    struct BaseWrapper: Base, py::wrapper<Base>{
      string foo() const{
        // if Base were abstract (non-instantiable in python), then
        // there would be only this->get_override("foo")() here
        //
        // if called on a class which overrides foo in python
        if(this->get_override("foo")) return this->get_override("foo")();
        // no override in python; happens if Base(Wrapper) is instantiated directly
        else return Base::foo();
      }
    };
    
    BOOST_PYTHON_MODULE(baz){
      py::class_<BaseWrapper,boost::noncopyable>("Base")
        .def("foo",&Base::foo)
        .def("fooBase",&Base::fooBase)
      ;
    }
    _
  2. bar.py

    _import sys
    sys.path.append('.')
    import baz
    
    class PyDerived(baz.Base):
      def foo(self): return 'PyDerived.foo'
    
    base=baz.Base()
    der=PyDerived()
    print base.foo(), base.fooBase()
    print der.foo(), der.fooBase()
    _
  3. Makefile

    _default:
           g++ -shared -fPIC -o baz.so baz.cpp -lboost_python `pkg-config python --cflags`
    _

そして結果は次のとおりです。

_Base.foo Base.foo
PyDerived.foo PyDerived.foo
_

ここで、fooBase()(非仮想c ++関数)が仮想foo()を呼び出す方法を確認できます。これは、c ++かPythonかに関係なくオーバーライドに解決されます。 C++のBaseからクラスを派生させることができ、それはまったく同じように機能します。

EDIT(c ++オブジェクトの抽出):

_PyObject* obj; // given
py::object pyObj(obj); // wrap as boost::python object (cheap)
py::extract<Base> ex(pyObj); 
if(ex.check()){ // types are compatible
  Base& b=ex(); // get the wrapped object
  // ...
} else {
  // error
}

// shorter, thrwos when conversion not possible
Base &b=py::extract<Base>(py::object(obj))();
_

_py::object_から_PyObject*_を作成し、_py::extract_を使用して、pythonオブジェクトが抽出しようとしているものと一致するかどうかをクエリします:PyObject* obj; py::extract<Base> extractor(py::object(obj)); if(!extractor.check()) /* error */; Base& b=extractor();

12
eudoxos

引用 http://wiki.python.org/moin/boost.python/Inheritance

「Boost.Pythonを使用すると、C++の継承関係を表すこともできるため、値、ポインター、または基本クラスへの参照が引数として期待される場合に、ラップされた派生クラスを渡すことができます。」

最初の部分(クラスMyCl(myif)を持つもの)を解決するための仮想関数の例があります

これを行う特定の例については、 http://wiki.python.org/moin/boost.python/OverridableVirtualFunctions

行myifc = MyCl();の場合python(モジュール)をC++に公開する必要があります。ここに例があります http://wiki.python.org/moin/boost.python/EmbeddingPython

10
Johan Lundberg

Eudoxosによる(非常に役立つ)回答 に基づいて、私は彼のコードを取得し、組み込みモジュールを備えた組み込みインタープリターが存在するように拡張しました。

この答えは、Boost.Pythonで 私のSWIGベースの答え に相当します。

ヘッダーファイルmyif.h:

class myif {
public:
  virtual float myfunc(float a) const { return 0; }
  virtual ~myif() {}
};

基本的には質問と同じですが、デフォルトの実装はmyfuncと仮想デストラクタです。

Pythonの実装、MyCl.pyについては、基本的に質問と同じです。

import myif

class MyCl(myif.myif):
  def myfunc(self,a): 
    return a*2.0

これにより、mymain.ccが残ります。そのほとんどは、Eudoxosからの回答に基づいています。

#include <boost/python.hpp>
#include <iostream>
#include "myif.h"

using namespace boost::python;

// This is basically Eudoxos's answer:
struct MyIfWrapper: myif, wrapper<myif>{
  float myfunc(float a) const {
    if(this->get_override("myfunc")) 
      return this->get_override("myfunc")(a);
    else 
      return myif::myfunc(a);
  }
};

BOOST_PYTHON_MODULE(myif){
  class_<MyIfWrapper,boost::noncopyable>("myif")
    .def("myfunc",&myif::myfunc)
  ;
}
// End answer by Eudoxos

int main( int argc, char ** argv ) {
  try {
    // Tell python that "myif" is a built-in module
    PyImport_AppendInittab("myif", initmyif);
    // Set up embedded Python interpreter:
    Py_Initialize();

    object main_module = import("__main__");
    object main_namespace = main_module.attr("__dict__");

    PySys_SetPath(".");
    main_namespace["mycl"] = import("mycl");

    // Create the Python object with an eval()
    object obj = eval("mycl.MyCl()", main_namespace);

    // Find the base C++ type for the Python object (from Eudoxos)
    const myif &b=extract<myif>(obj)();
    std::cout << b.myfunc(5) << std::endl;

  } catch( error_already_set ) {
    PyErr_Print();
  }
}

ここに追加した重要な部分は、「Boost.Pythonを使用してPythonを埋め込むにはどうすればよいですか?」」と「Python = Boost.pythonを使用しますか?」(Eudoxosが回答しました)は、「同じプログラムで両方を同時に実行するにはどうすればよいですか?」という質問に対する答えです。これに対する解決策は PyImport_AppendInittab 呼び出し。これは、モジュールがロードされたときに通常呼び出される初期化関数を受け取り、それを組み込みモジュールとして登録します。したがって、mycl.pyがimport myif組み込みのBoost.Pythonモジュールをインポートすることになります。

8
Flexo

Boost Pythonを見てください。これは、C++とPythonの間を橋渡しする最も用途が広く強力なツールです。

http://www.boost.org/doc/libs/1_48_0/libs/python/doc/

1
pyroscope