2013-05-15 33 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所述。