将Boost::Python::object作为参数传递给Python函数

Boost.Python - Passing boost::python::object as argument to python function?

本文关键字:Python 参数传递 函数 object Boost      更新时间:2023-10-16

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

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

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

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

有没有任何方法可以将对象直接传递给类似的python函数,或者有任何其他方法可以实现我想要的结果?

提前感谢您的帮助。:)

从c++sig邮件列表中获得了这个出色的解决方案。

在C++类中实现一个std::map<std::string, boost::python::object>,然后重载__getattr__()__setattr__()以读取和写入该std::映射。然后像往常一样用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__