boost python,使用除 main global 之外的命名空间
Posted
技术标签:
【中文标题】boost python,使用除 main global 之外的命名空间【英文标题】:boost python, using a namespace other than 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__");
其中main
和main_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() 都已定义。
如果有人能指出工作代码的草图,我将不胜感激。我找不到关于这个问题的相关资料。
【问题讨论】:
【参考方案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 *** thread 的说明管理“线程”状态。
【讨论】:
以上是关于boost python,使用除 main global 之外的命名空间的主要内容,如果未能解决你的问题,请参考以下文章
我的 Py_NoneStruct 符号(python、boost.python)在哪里?
*.pyd 库中的 C++ Boost Python 方法不起作用