【问题标题】:boost python, using a namespace other than main globalboost python,使用除 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() 都已定义。

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

【问题讨论】:

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


    【解决方案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 stackoverflow thread 的说明管理“线程”状态。

      【讨论】:

        猜你喜欢
        • 2010-12-05
        • 2018-10-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-05
        • 1970-01-01
        • 2017-01-10
        相关资源
        最近更新 更多