在C++中从局部作用域创建Python对象

2 投票
1 回答
1059 浏览
提问于 2025-04-17 21:28

我正在使用boost.python把两个C++类提供给Python使用。

class X {
   public:
      X();
}
class Y {
   ...
}

BOOST_PYTHON_MODULE(test) {
    class_<X>("X", init<>());
    class_<Y>("Y", init<>());
}

每当我在Python中创建一个新的X时,我希望能在C++中运行一些代码,这段代码会创建一个类型为Y的本地对象'y'。所以实际上,当我在Python中执行

x = X()

时,我希望这段代码也能运行

y = Y()

,但要从C++中的X::X()构造函数来执行。

我想在X::X()构造函数中使用类似

scope().attr("y")=...

的方式,但每次这样调用时,范围总是返回一个NoneType对象(如果我在BOOST_PYTHON_MODULE中使用这个构造是可以正常工作的,但那不是我想要的地方)。

1 个回答

2

boost::python::scope 更像是命名空间,而不是代码块的作用域。Python/C API 通过PyEval_GetLocals()函数暴露了一个类似于locals()的字典。我们可以利用这个字典将变量注入到当前的作用域中。

// Borrow a reference from the locals dictionary to create a handle.
// If PyEval_GetLocals() returns NULL, then Boost.Python will throw.
namespace python = boost::python;
python::object locals(python::borrowed(PyEval_GetLocals()));

// Inject an instance of Y into the frame's locals as variable 'y'.
// Boost.Python will handle the conversion of C++ Y to Python Y.
locals["y"] = Y();

下面是一个完整的例子,当构造example.X时,会将example.Y的实例作为变量y注入到调用者的作用域中。

#include <boost/python.hpp>

/// @brief Mockup types.
struct X {};
struct Y {};

/// @brief Auxiliary function that will create X and inject an Y object
///        as 'y' into the caller's frame.
X* make_x_and_inject_y()
{
  // Boost.Python objects may throw, so use a smart pointer that can
  // release ownership to manage memory.
  std::auto_ptr<X> x(new X());

  // Borrow a reference from the locals dictionary to create a handle.
  // If PyEval_GetLocals() returns NULL, then Boost.Python will throw.
  namespace python = boost::python;
  python::object locals(python::borrowed(PyEval_GetLocals()));

  // Inject an instance of Y into the frame's locals as variable 'y'.
  // Boost.Python will handle the conversion of C++ Y to Python Y.
  locals["y"] = Y();

  // Transfer ownership of X to Boost.Python.
  return x.release();
}

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;

  // Expose X, explicitly suppressing Boost.Python from creating a
  // default constructor, and instead exposing a custom constructor.
  python::class_<X>("X", python::no_init)
    .def("__init__", python::make_constructor(&make_x_and_inject_y))
    ;
  python::class_<Y>("Y", python::init<>());
}

交互使用:

>>> import example
>>> def fun():
...     assert('y' not in dir())
...     example.X()
...     assert('y' in dir()) # creating X injects y into scope
... 
>>> assert('y' not in dir())
>>> fun()
>>> assert('y' not in dir())
>>> example.X()
<example.X object at 0xb746fa7c>
>>> assert('y' in dir()) # creating X injects y into scope
>>> assert(isinstance(y, example.Y))

在这个实现中,我选择将一个辅助工厂函数暴露给Python,作为X的构造函数,而不是让X的C++构造函数来执行Y的注入。这只是个人偏好,但我发现这样可以更清晰地区分两种语言,减少C++类型对Python的了解。

撰写回答