在提供解决方案之前,我想澄清一下 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 符号而失败。