我使用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
实例都有自己的“全局”命名空间。 / 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()。
如果有人能指出我的工作代码草图,我将非常感激。我找不到有关这个问题的相关材料。
答案 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 ++代码需要:
__dict__
。__builtin__
模块注入Python对象的__dict__
。__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的说明管理“线程”状态。