0

我正在使用 boost python 将 python 嵌入到我的 C++ 应用程序中。我是一名 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);
}

这里的问题是,不同的 C++ 实例PyExpression修改了相同的全局 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() 都已定义。

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

4

2 回答 2

3

在提供解决方案之前,我想对 Python 行为做一些说明。

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

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

上面的代码导入了一个名为__main__. 在 Python 中,由于导入行为,模块本质上是单例。因此,尽管 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']
    

Pythonexec语句有两个可选参数:

  • 第一个参数指定将用于的字典globals()。如果省略第二个参数,那么它也用于locals().
  • 第二个参数指定将用于的字典locals()。内部发生的变量变化exec应用于locals().

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

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

为了解决这些行为,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需要 using。否则,import time将由于未定义的PyExc_IOError符号而失败。

于 2013-05-16T18:40:34.217 回答
0

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

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

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

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

于 2013-05-16T13:48:36.947 回答