使用除主全局之外的命名空间来提升python

时间:2013-05-15 15:26:32

标签: c++ namespaces boost-python python-embedding

我使用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实例都有自己的“全局”命名空间。 / p>

如果我传递的是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()。

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

2 个答案:

答案 0 :(得分:3)

在提供解决方案之前,我想提供一些关于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__模块可以在传递给__builtins__的命名空间中作为exec进行评估。
  • 如果一个名为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符号而失败。

答案 1 :(得分:0)

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

这是未经测试的,但我猜想这可以做到这一点:

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

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