2013-05-15 34 views
0

我在使用boost python的C++应用程序中嵌入了python。我是一名C++程序员,对Python的知识非常有限。boost 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++情况下修改同一个全局命名空间蟒,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模块,并再次,该命名空间的PyExpression所有实例之间共享。

总之,我希望能够使用一个命名空间的对象/字典,最好是PyExpression类成员,有PyExpression只有该实例可以访问命名空间,命名空间表现得像一个全局命名空间这样可以导入其他模块,并且全部定义了`globals(),locals(),vars()。

如果任何人都可以指向我的工作代码草图,我将非常感激。我无法找到有关此问题的相关材料。

回答

2

在提供解决方案之前,我想提供一些关于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__是含有特定实例变量的字典。这将包含相当于C++的非静态成员变量。

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

Python的exec声明有两个可选参数:

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

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

  • 虽然import是一个Python关键字,CPython的实现依赖于__builtins__.__import__。因此,需要保证在传递给exec的名称空间内__builtin__模块可评估为__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(); 
    } 
} 

和输出:

[[email protected]]$ ./a.out 
2013 
5 

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

0

Python没有为这类任务提供100%可靠的隔离机制。也就是说,您正在寻找的基本工具是Python C-API Py_NewInterpreter,它是documented here。创建一个新的(半)分离环境(N.B .:析构函数应该调用Py_EndInterpreter)后,您将不得不打电话给它。

这是未经测试,但我猜的东西liket这将做的工作:

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

,你可以换到这一点的对象。如果您希望这样做,您必须必须管理“线程”状态,如in this other stackoverflow thread所述。