boost python,使用除 main global 之外的命名空间

Posted

技术标签:

【中文标题】boost python,使用除 main global 之外的命名空间【英文标题】:boost python, using a namespace other than main global 【发布时间】:2013-05-15 15:26:32 【问题描述】:

我正在使用 boost python 在我的 C++ 应用程序中嵌入 python。我是一名 C++ 程序员,对 Python 的了解非常有限。

我有一个 C++ 类,PyExpression。这个类的每个实例都有一个字符串expStr,它是一个简短的用户输入(在运行时)python 程序,通过调用boost::python::exec 来执行。简而言之,我将其设置为:

//import main and its globals
bp::object main = bp::import("__main__");
bp::object main_namespace = main.attr("__dict__"); 

其中mainmain_namespace 是C++ 类PyExpression 的成员。

void PyExpression::Run()

    bp::object pyrun = exec(expStr,main_namespace);

这里的问题是PyExpression 的不同C++ 实例修改了相同的全局python 命名空间main_namespace,我希望每个PyExpression 实例都有自己的“全局”命名空间。

如果我传入boost::python::dict class_dict 而不是上面的main_namespace,它在基本级别上工作。但是如果 PyExpression::expStr 导入一个模块,例如import sys,然后我得到一个ImportError。另外,使用class_dict,我不能再调用globals()locals()vars(),因为它们都变得未定义。

我还尝试将PyExpression 公开为python 模块。简而言之,

BOOST_PYTHON_MODULE(PyExpModule)

    bp::class_<PyExpression>("PyExpression", bp::no_init)
    //a couple .def functions


int pyImport = PyImport_AppendInittab( "PyExpModule", &initPyExpModule );

bp::object thisExpModule = bp::object( (bp::handle<>(PyImport_ImportModule("PyExpModule"))) );
bp::object PyExp_namespace = thisExpModule.attr("__dict__");

不幸的是,使用PyExp_namespace,当要执行的字符串导入python模块时,我再次收到ImportError,并且命名空间在PyExpression的所有实例之间共享。

简而言之,我希望能够使用命名空间对象/字典,最好是PyExpression 的类成员,只有PyExpression 的实例可以访问命名空间,并且命名空间的行为类似于一个全局命名空间,以便可以导入其他模块,并且 `globals()、locals()、vars() 都已定义。

如果有人能指出工作代码的草图,我将不胜感激。我找不到关于这个问题的相关资料。

【问题讨论】:

【参考方案1】:

在提供解决方案之前,我想澄清一下 Python 的行为。

Boost.Python 的object 本质上是智能指针的高级句柄。因此,多个 object 实例可能指向同一个 Python 对象。

object main_module = import("__main__");
object main_namespace = main_module.attr("__dict__");

上面的代码导入了一个名为__main__的模块。在 Python 中,由于import behavior,模块本质上是单例的。因此,尽管 C++ main_module 可能是 C++ PyExpression 类的成员,但它们都指向同一个 Python __main__ 模块,因为它是一个单例。这导致main_namespace 指向同一个命名空间。

大部分 Python 都是围绕字典构建的。例如,使用 example 模块:

class Foo:
    def __init__(self):
        self.x = 42;

    def bar(self):
        pass

有3个兴趣词典:

example.__dict__example 模块的命名空间。

>>> example.__dict__.keys()
['__builtins__', '__file__', '__package__', '__name__', 'Foo', '__doc__']

example.Foo.__dict__ 是描述Foo 类的字典。此外,它将包含 C++ 的静态成员变量和函数的等价物。

>>> example.Foo.__dict__.keys()
['__module__', 'bar', '__doc__', '__init__']

example.Foo().__dict__ 是一个包含实例特定变量的字典。这将包含相当于 C++ 的非静态成员变量。

>>> example.Foo().__dict__.keys()
['x']

Python exec 语句有两个可选参数:

第一个参数指定将用于globals() 的字典。如果省略第二个参数,则它也用于locals()。 第二个参数指定将用于locals() 的字典。 exec 中发生的变量更改将应用​​于 locals()

要获得所需的行为,example.Foo().__dict__ 需要用作locals()。不幸的是,由于以下两个因素,这变得稍微复杂一些:

虽然import 是Python 关键字,但CPython 实现依赖于__builtins__.__import__。因此,需要保证__builtin__ 模块在传递给exec 的命名空间内可评估为__builtins__。 如果名为 Foo 的 C++ 类通过 Boost.Python 公开为 Python 类,则无法从 C++ Foo 实例中访问 Python Foo 实例。

为了解决这些行为,C++ 代码需要:

获取 Python 对象的 __dict__ 的句柄。 将__builtin__ 模块注入Python 对象的__dict__。 从 Python 对象中提取 C++ 对象。 将 Python 对象的 __dict__ 传递给 C++ 对象。

这是一个示例解决方案,它只在要评估代码的实例上设置变量:

#include <boost/python.hpp>

class PyExpression

public:
  void run(boost::python::object dict) const
  
    exec(exp_.c_str(), dict);
  
  std::string exp_;
;

void PyExpression_run(boost::python::object self)

  // Get a handle to the Python object's __dict__.
  namespace python = boost::python;
  python::object self_dict = self.attr("__dict__");

  // Inject the __builtin__ module into the Python object's __dict__.
  self_dict["__builtins__"] = python::import("__builtin__");

  // Extract the C++ object from the Python object.
  PyExpression& py_expression = boost::python::extract<PyExpression&>(self);

  // Pass the Python object's `__dict__` to the C++ object.
  py_expression.run(self_dict);


BOOST_PYTHON_MODULE(PyExpModule)

  namespace python = boost::python;
  python::class_<PyExpression>("PyExpression")
    .def("run", &PyExpression_run)
    .add_property("exp", &PyExpression::exp_, &PyExpression::exp_)
    ;


// Helper function to check if an object has an attribute.
bool hasattr(const boost::python::object& obj,
             const std::string& name)

  return PyObject_HasAttrString(obj.ptr(), name.c_str());


int main()

  PyImport_AppendInittab("PyExpModule", &initPyExpModule);
  Py_Initialize();

  namespace python = boost::python;
  try
  
    // python: import PyExpModule
    python::object py_exp_module = python::import("PyExpModule");

    // python: exp1 = PyExpModule.PyExpression()
    // python: exp1.exp = "import time; x = time.localtime().tm_year"
    python::object exp1 = py_exp_module.attr("PyExpression")();
    exp1.attr("exp") = 
      "import time;"
      "x = time.localtime().tm_year"
      ;

    // python: exp2 = PyExpModule.PyExpression()
    // python: exp2.exp = "import time; x = time.localtime().tm_mon"
    python::object exp2 = py_exp_module.attr("PyExpression")();
    exp2.attr("exp") = 
      "import time;"
      "x = time.localtime().tm_mon"
      ;

    // Verify neither exp1 nor exp2 has an x variable.
    assert(!hasattr(exp1, "x"));
    assert(!hasattr(exp2, "x"));

    // python: exp1.run()
    // python: exp2.run()
    exp1.attr("run")();
    exp2.attr("run")();

    // Verify exp1 and exp2 contain an x variable.
    assert(hasattr(exp1, "x"));
    assert(hasattr(exp2, "x"));

    // python: print exp1.x
    // python: print exp2.x
    std::cout << python::extract<int>(exp1.attr("x")) 
      << "\n" << python::extract<int>(exp2.attr("x"))
      << std::endl;
  
  catch (python::error_already_set&)
  
    PyErr_Print();
  

还有输出:

[twsansbury@localhost]$ ./a.out 
2013
5

由于库是如何从导入中加载的,它可能需要向链接器提供参数,这将导致所有符号(不仅是使用的符号)进入动态符号表。例如,当使用 gcc 编译上述示例时,需要使用 -rdynamic。否则,import time 将由于未定义的 PyExc_IOError 符号而失败。

【讨论】:

【参考方案2】:

Python 没有为这类任务提供 100% 可靠的隔离机制。也就是说,您正在寻找的基本工具是 Python C-API Py_NewInterpreter,即 documented here。您必须在创建 PyExpression 对象时调用它,以创建一个新的(半)隔离环境(注意:析构函数应该调用 Py_EndInterpreter)。

这是未经测试的,但我猜想这样可以完成这项工作:

PyThreadState* current_interpreter = Py_NewInterpreter();
bp::object pyrun = exec(expStr);
Py_EndInterpreter(current_interpreter);

您可以将其包装到一个对象中。如果您希望这样做,您必须按照in this other *** thread 的说明管理“线程”状态。

【讨论】:

以上是关于boost python,使用除 main global 之外的命名空间的主要内容,如果未能解决你的问题,请参考以下文章

我的 Py_NoneStruct 符号(python、boost.python)在哪里?

Boost python,使用命名空间调用函数对象

*.pyd 库中的 C++ Boost Python 方法不起作用

将 boost 类型擦除类型转换回原始类型给了我 boost::bad_any_cast

Boost.Python 和 CMake 链接和加载错误

Boost python /从线程导入模块需要ReleaseLock()。为啥?