使用boost python将python变量设置为C++对象指针

Set a python variable to a C++ object pointer with boost-python

本文关键字:python C++ 对象 指针 变量 boost 使用 设置      更新时间:2023-10-16

我想从C++中设置一个Python变量,这样C++程序就可以创建一个对象Game* game = new Game();,以便Python代码能够引用这个实例(以及调用函数等)。我怎样才能做到这一点?

我觉得我对Python或Boost Python的工作方式有一些核心误解。

main_module.attr("game") = game在try-catch语句中,错误(使用PyErr_Fetch)为"找不到C++类型的to_python(按值)转换器:class Game"。

例如

class_<Game>("Game")
        .def("add", &Game::add)
;
object main_module = import("__main__");
Game* game = new Game();
main_module.attr("game") = game; //This does not work

来自Python:

import testmodule
testmodule.game.foo(7)

在处理语言绑定时,通常必须在细节上过于迂腐。默认情况下,当C++对象越过语言边界时,Boost.Python将创建一个副本,因为这是防止悬挂引用的最安全的操作方式。如果不应该复制,那么就需要明确C++对象的所有权:

  • 要在保持C++所有权的同时将对C++对象的引用传递给Python,请使用boost::python::ptr()boost::ref()。C++代码应该保证C++对象的生存期至少与Python对象一样长。使用ptr()时,如果指针为null,则生成的Python对象将为None
  • 要将C++对象的所有权转移到Python,可以应用manage_new_object ResultConverterGenerator,从而将所有权转移到Python。一旦Python对象的生存期结束,C++代码就不应该试图访问指针
  • 对于共享所有权,需要使用支持共享语义(如boost::shared_ptr)的智能指针的HeldType来公开类

一旦创建了Python对象,就需要将其插入到Python命名空间中才能进行访问:

  • 在模块定义中,使用boost::python::scope获取当前作用域的句柄。例如,下面将把x插入example模块:

    BOOST_PYTHON_MODULE(example)
    {
      boost::python::scope().attr("x") = ...; // example.x
    }
    
  • 要插入__main__模块,可以导入__main__。例如,下面将把x插入__main__模块:

    boost::python::import("__main__").attr("x") = ...;
    

下面是一个示例,演示如何从C++直接构造Python对象,将C++对象的所有权转移到Python,以及构造引用C++对象的Python对象:

#include <iostream>
#include <boost/python.hpp>
// Mockup model.
struct spam
{
  spam(int id)
    : id_(id)
  {
    std::cout << "spam(" << id_ << "): "  << this << std::endl;
  }
  ~spam()
  {
    std::cout << "~spam(" << id_ << "): " << this << std::endl;
  }
  // Explicitly disable copying.
  spam(const spam&) = delete;
  spam& operator=(const spam&) = delete;
  int id_;
};
/// @brief Transfer ownership to a Python object.  If the transfer fails,
///        then object will be destroyed and an exception is thrown.
template <typename T>
boost::python::object transfer_to_python(T* t)
{
  // Transfer ownership to a smart pointer, allowing for proper cleanup
  // incase Boost.Python throws.
  std::unique_ptr<T> ptr(t);
  // Use the manage_new_object generator to transfer ownership to Python.
  namespace python = boost::python;
  typename python::manage_new_object::apply<T*>::type converter;
  // Transfer ownership to the Python handler and release ownership
  // from C++.
  python::handle<> handle(converter(*ptr));
  ptr.release();
  return python::object(handle);
}
namespace {
spam* global_spam;
} // namespace
BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  // Expose spam.
  auto py_spam_type = python::class_<spam, boost::noncopyable>(
      "Spam", python::init<int>())
    .def_readonly("id", &spam::id_)
    ;
  // Directly create an instance of Python Spam and insert it into this
  // module's namespace.
  python::scope().attr("spam1") = py_spam_type(1);
  // Construct of an instance of Python Spam from C++ spam, transfering
  // ownership to Python.  The Python Spam instance will be inserted into
  // this module's namespace.
  python::scope().attr("spam2") = transfer_to_python(new spam(2));
  // Construct an instance of Python Spam from C++, but retain ownership of
  // spam in C++.  The Python Spam instance will be inserted into the
  // __main__ scope.
  global_spam = new spam(3);
  python::import("__main__").attr("spam3") = python::ptr(global_spam);
}

交互式使用:

>>> import example
spam(1): 0x1884d40
spam(2): 0x1831750
spam(3): 0x183bd00
>>> assert(1 == example.spam1.id)
>>> assert(2 == example.spam2.id)
>>> assert(3 == spam3.id)
~spam(1): 0x1884d40
~spam(2): 0x1831750

在示例用法中,请注意Python在退出时没有销毁spam(3),因为它没有被授予底层对象的所有权。