Boost.Python - 将boost::python::object作为参数传递给Python函数?

6
我正在一个小项目中使用Python作为嵌入式脚本引擎,目前使用boost.python没有遇到太多问题,但是如果可能的话,我想做一些其他的事情。基本上,Python可以通过添加函数和甚至数据值来扩展我的C++类。我希望能够将它们持久化在C++端,这样一个Python函数可以向类中添加数据成员,然后稍后传递给不同函数的相同实例仍将具有这些成员。这里的目标是在C++中编写通用核心引擎,并允许用户以任何需要的方式在Python中扩展它,而无需接触C++。

我认为可以将boost::python::object作为值self存储在C++类中,当从C++调用Python时,我会通过boost::python::ptr()发送该Python对象,以便Python端的修改将持久保存到C++类中。不幸的是,尝试这样做时,我得到以下错误:

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

是否有任何直接将对象传递给Python函数的方法?或者有其他的方法可以实现我想要的结果吗?谢谢提前的回答 :)

找到了一个解决方案。在Python中,我添加了一个函数:def ProcessEvent(event, obj): return globals()[event](obj.PyObj)在C++中,我向我的C++类添加了一个名为PyObjboost::python::object,它被初始化为boost::python::ptr(this),并使用它调用了我的事件,将我想要调用的事件名称作为第一个参数传递,并将我想要传递给它的对象的boost::python::ptr作为第二个参数传递。这正如我所希望的那样工作-当我在一个事件中添加了一个属性时,当我将其传递给另一个事件时,它仍然存在。也许不是最好的解决方案...但是,它有效。 - Závada LaCroix
1个回答

6

我从c++sig邮件列表中得到了这个绝妙的解决方案。

在C++类中实现一个std::map<std::string, boost::python::object>,然后重载__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模块,添加您想要的任何其他def和属性:
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__中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接