Boost.python 将对象拉取到本地范围内以进行读取访问

Boost.python pull object into local scope for read access

本文关键字:范围内 访问 读取 python 对象 Boost      更新时间:2023-10-16

我正在使用Boost.Python将C++函数导出到Python。在这个函数中,我想访问本地堆栈帧上的 Python 对象,而不需要在 Python 端访问这个对象。

示例:我有一个C++对象X和一个C++函数cppfun导出到 Python,然后按照代码,

x = X()
def fun():
    cppfun()
    # no explicit access to x

cppfun内部,我可以使用 PyEval_GetLocals 访问本地堆栈帧,但由于Python 端不包含任何对 x 的引用,这个对象不会在该本地堆栈帧中(类似地,如果你print(locals()) fun 内部,x将不存在,除非你添加对它的引用,例如 print(x))。

有什么方法可以在cppfun内部访问x,也就是说,我可以以某种方式强制 Python 将其拉入locals而不在 Python 端的fun内访问它吗?我尝试简单地运行boost::python::eval("x")但这也在错误的范围内运行。

添加:所以我不想让外框架的xfun或类似的东西中可写(我知道这是不可能的); 这个问题纯粹是关于如何从外部框架获得对变量的读取访问权限,而无需在 Python 端访问它。

可以通过使用 PyEval_GetGlobals() 来访问外部作用域,它返回当前执行中全局变量的字典。 它与 Python 内置的 globals() 函数非常相似,后者返回定义函数的模块的全局符号表。 但是,标准模块和扩展模块之间的帧堆栈处理方式略有不同。 对于扩展模块,当前执行是调用方,因此PyEval_GetGlobals()将返回调用方模块的全局符号,而不是扩展模块的全局符号。

或者,可以通过PyEval_GetFrame()获取当前帧的句柄,然后遍历堆栈,检查每个的本地(f_local)和全局(f_global)字典。


这是一个完整的最小示例,演示了这两种技术以及它们之间的细微差异。 在示例中,get_global()将使用PyEval_GetGlobals()search_stack_locals()将检查堆栈中每个帧的本地字典。

#include <boost/python.hpp>
namespace detail {
/// @brief Return a handle to a global variable.  If one is not found, then
///        None is returned.
boost::python::object get_global(std::string name)
{
  // Borrow a reference from the locals dictionary to create a handle.
  // If PyEval_GetGlobals() returns NULL, then Boost.Python will throw.
  namespace python = boost::python;
  python::dict globals(python::borrowed(PyEval_GetGlobals()));
  return globals.get(name);
}
/// @brief Search through the call stack for a variable.  If found, the
///        object is returned.  Otherwise, None.
boost::python::object search_stack_locals(std::string name)
{
  // Get a handle to the current frame.
  namespace python = boost::python;
  python::object frame(python::borrowed(
    reinterpret_cast<PyObject*>(PyEval_GetFrame())));
  // Iterate through the stack's frames until the variable has been found
  // or the stack has been exhausted.
  while (frame)
  {
    // If the current frame has the desired variable, then return it.
    python::dict locals(frame.attr("f_locals"));
    if (locals.has_key(name))
    {
      return locals.get(name);
    }
    // Move up the stack.
    frame = frame.attr("f_back");
  }
  // Return None
  return python::object();
}
} // namespace detail
/// @brief Mockup function to demonstrate finding non-local variables.
boost::python::object cppfun(std::string name)
{
  return boost::python::make_tuple(
    detail::get_global(name), detail::search_stack_locals(name));
}
BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::def("cppfun", &cppfun);
}

spam.py

import example
x = 'spam.global.x'
y = 'spam.global.y'
def inner():
    for name in ('x', 'y', 'z'):
        print name, example.cppfun(name)
def outer():
    x = 'spam.outer.x'
    inner()

交互式用法:

>>> x = 'main.global.x'
>>> y = 'main.global.y'
>>> z = 'main.global.z'
>>> import spam
>>> spam.outer()
x ('spam.global.x', 'spam.outer.x')
y ('spam.global.y', 'main.global.y')
z (None, 'main.global.z')

请注意,当使用 PyEval_GetGlobals() 时,example扩展模块使用调用方模块的全局符号表 (spam )。 解释器主命名空间中声明的全局变量仅在迭代堆栈时找到。