6

そこで、組み込みスクリプト エンジンとして Python を使用する小さなプロジェクトに取り組んでいます。これまでboost.pythonを使って苦労したことはありませんが、できればやりたいことがあります。

基本的に、Python を使用して、クラスに関数やデータ値を追加することで C++ クラスを拡張できます。これらを C++ 側で永続化できるようにしたいので、1 つの python 関数でデータ メンバーをクラスに追加できます。その後、別の関数に渡された同じインスタンスが引き続きそれらを保持します。ここでの目標は、C++ でジェネリック コア エンジンを記述し、ユーザーが C++ に触れることなく必要な方法で Python で拡張できるようにすることです。

だから私がうまくいくと思ったのは、 aboost::python::objectを C++ クラスに value として格納し、C++selfから python を呼び出すときに、その python オブジェクトを を介して送信することでしboost::python::ptr()た。 C++ クラス。残念ながら、これを試してみると、次のエラーが表示されます。

TypeError: No to_python (by-value) converter found for C++ type: boost::python::api::object

そのようなPython関数にオブジェクトを直接渡す方法はありますか、それとも他の方法で目的の結果を達成することができますか?

助けてくれてありがとう。:)

4

1 に答える 1

6

c++sigメーリングリストからこの素晴らしいソリューションを入手しました。

std::map<std::string, boost::python::object>C ++クラスにを実装してから、オーバーロード__getattr__()__setattr__()て、そのstd::mapからの読み取りと書き込みを行います。次にboost::python::ptr()、通常どおりにPythonに送信します。オブジェクトをC ++側に保持したり、Pythonに送信したりする必要はありません。それは完璧に動作します。

編集:私はまた、__setattr__()私が追加したものを壊していたので、特別な方法で関数をオーバーライドする必要があることに気づきましたadd_property()。Pythonは、を呼び出す前にクラスの属性をチェックするため、これらを取得するときに問題なく機能しましたが、。を使用した__getattr__()そのようなチェックはありません__setattr__()。直接呼び出すだけです。そのため、これを完全なソリューションに変えるためにいくつかの変更を加える必要がありました。ソリューションの完全な実装は次のとおりです。

まず、グローバル変数を作成します。

boost::python::object PyMyModule_global;

次のようにクラスを作成します(他の情報を追加したい場合)。

class MyClass
{
public:
   //Python checks the class attributes before it calls __getattr__ so we don't have to do anything special here.
   boost::python::object Py_GetAttr(std::string str)
   {
      if(dict.find(str) == dict.end())
      {
         PyErr_SetString(PyExc_AttributeError, JFormat::format("MyClass instance has no attribute '{0}'", str).c_str());
         throw boost::python::error_already_set();
      }
      return dict[str];
   }

   //However, with __setattr__, python doesn't do anything with the class attributes first, it just calls __setattr__.
   //Which means anything that's been defined as a class attribute won't be modified here - including things set with
   //add_property(), def_readwrite(), etc.
   void Py_SetAttr(std::string str, boost::python::object val)
   {
      try
      {
         //First we check to see if the class has an attribute by this name.
         boost::python::object obj = PyMyModule_global["MyClass"].attr(str.c_str());
         //If so, we call the old cached __setattr__ function.
         PyMyModule_global["MyClass"].attr("__setattr_old__")(ptr(this), str, val);
      }
      catch(boost::python::error_already_set &e)
      {
         //If it threw an exception, that means that there is no such attribute.
         //Put it on the persistent dict.
         PyErr_Clear();
         dict[str] = val;
      }
   }
private:
   std::map<std::string, boost::python::object> dict;
};

次に、Pythonモジュールを次のように定義し、必要な他の定義とプロパティを追加します。

BOOST_PYTHON_MODULE(MyModule)
{
   boost::python::class_<MyClass>("MyClass", boost::python::no_init)
      .def("__getattr__", &MyClass::Py_GetAttr)
      .def("__setattr_new__", &MyClass::Py_SetAttr);
}

次に、Pythonを初期化します。

void PyInit()
{
   //Initialize module
   PyImport_AppendInittab( "MyModule", &initMyModule );
   //Initialize Python
   Py_Initialize();

   //Grab __main__ and its globals
   boost::python::object main = boost::python::import("__main__");
   boost::python::object global = main.attr("__dict__");

   //Import the module and grab its globals
   boost::python::object PyMyModule = boost::python::import("MyModule");
   global["MyModule"] = PyMyModule;
   PyMyModule_global = PyMyModule.attr("__dict__");

   //Overload MyClass's setattr, so that it will work with already defined attributes while persisting new ones
   PyMyModule_global["MyClass"].attr("__setattr_old__") = PyMyModule_global["MyClass"].attr("__setattr__");
   PyMyModule_global["MyClass"].attr("__setattr__") = PyMyModule_global["MyClass"].attr("__setattr_new__");
}

これらすべてを実行すると、Pythonで行われたインスタンスへの変更をC++に永続化できるようになります。C ++で属性として定義されているものはすべて適切に処理され、そうでないものはすべてdictクラスのの代わりに追加され__dict__ます。

于 2012-12-06T10:09:38.883 に答える