6

所以我正在做一个小项目,我在其中使用 Python 作为嵌入式脚本引擎。到目前为止,我使用 boost.python 并没有遇到太多麻烦,但如果可能的话,我想用它做一些事情。

基本上,Python 可用于通过向类添加函数甚至数据值来扩展我的 C++ 类。我希望能够让这些在 C++ 端持久化,所以一个 python 函数可以将数据成员添加到一个类,然后传递给不同函数的同一个实例仍然会有它们。这里的目标是用 C++ 编写一个通用的核心引擎,并让用户在 Python 中以他们需要的任何方式对其进行扩展,而无需接触 C++。

所以我认为可行的是我将 aboost::python::object作为值存储在 C++ 类中self,当从 C++ 调用 python 时,我会通过 发送该 python 对象boost::python::ptr(),以便 python 端的修改将持续返回到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++ 类中实现 a ,然后重载__getattr__()__setattr__()读取和写入该 std::map。然后像往常一样将它发送到 python boost::python::ptr(),无需在 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 回答