使用解释器
看到这里,你应该已经掌握了如何用 Boost.Python 从 Python 调用 C++ 代码。但现实总要更复杂一些——有时候你需要反过来:从 C++ 端去调用 Python 代码。这就需要你把 Python 解释器“嵌入”到 C++ 程序中。
目前,Boost.Python 并没有把嵌入所需的一切功能都打包好,所以你还需要借助 Python/C API 来补上那些缺口。不过,Boost.Python 已经让嵌入这件事变得容易了不少,而且可以期待的是,在后续版本中,你甚至完全不需要碰 Python/C API。这一点值得持续关注。
构建嵌入式程序
想要把 Python 嵌入到你的程序里,链接时需要连上 Boost.Python 和 Python 自身的运行时库。
Boost.Python 的库有两个变体,都放在 Boost 的 /libs/python/build/bin-stage 子目录下。在 Windows 上,发布版叫 boost_python.lib,调试版叫 boost_python_debug.lib。如果你找不到对应库,那大概率是还没构建 Boost.Python。
Python 的库则在 Python 目录下的 /libs 子目录中。Windows 上它叫 pythonXY.lib,其中 XY 对应你的 Python 主版本号。
另外,记得把 Python 的 /include 子目录加到包含路径里。
用 Jamfile 来组织这一切的话,大致是这样:
projectroot c:projectsembedded_program ; # location of the program
# bring in the rules for python
SEARCH on python.jam = $(BOOST_BUILD_PATH) ;
include python.jam ;
exe embedded_program # name of the executable
: # sources
embedded_program.cpp
: # requirements
boost_python
c:boostlibspython$(PYTHON_PROPERTIES)
$(PYTHON_LIB_PATH)
$(PYTHON_EMBEDDED_LIBRARY) ;
入门
能构建工程是好事,但还缺真正要运行的东西。想要把 Python 解释器嵌入到你的 C++ 程序里,需要按顺序完成四个步骤:
#include- 调用
Py_Initialize()启动解释器,并创建__main__模块。 - 调用其他 Python C API 例程来使用解释器。
- (注意:此时还不能调用
Py_Finalize()来停止解释器。这个问题可能会在未来的 boost.python 版本中得到解决。)
现在我们可以把解释器嵌入到程序里了,接下来看看怎么用它。
使用解释器
你可能已经知道,Python 中的对象是引用计数的。Python C API 的 PyObject 自然也沿用这一机制。但区别在于:Python 内部引用计数是全自动的,而 Python C API 却要求你手动处理。这很麻烦,而且一旦碰上 C++ 异常,手动管理更是容易出问题。好在 Boost.Python 提供了 handle 和 object 类模板,帮我们把这一过程自动化了。
运行 Python 代码
Boost.Python 提供了三个函数,用来从 C++ 端运行 Python 代码:
object eval(str expression, object globals = object(), object locals = object())
object exec(str code, object globals = object(), object locals = object())
object exec_file(str filename, object globals = object(), object locals = object())
eval 计算给定的表达式并返回结果值;exec 执行一段代码(通常是一组语句),它不返回值(但你可以通过字典拿到结果);exec_file 则直接执行一个文件中的代码。
globals 和 locals 参数都是 Python 字典,用于指定代码运行时的全局与局部变量上下文。在大多数场景下,直接拿 __main__ 模块的命名空间字典来当这两个参数就足够了。
此外,Boost.Python 还提供了导入模块的功能:
object import(str name)
它会导入指定的 Python 模块(必要时会将其加载到当前进程中),然后返回该模块对象。
看个例子:导入 __main__ 模块,并在它的命名空间里执行一段 Python 代码。
object main_module = import("__main__");
object main_namespace = main_module.attr("__dict__");
object ignored = exec("hello = file('hello.txt', 'w')\n"
"hello.write('Hello world!')\n"
"hello.close()",
main_namespace);
这段代码会在当前目录创建一个叫 hello.txt 的文件,里面写着那句编程圈无人不晓的问候语。
操纵 Python 对象
很多时候我们需要一个类来直接操作 Python 对象。其实上一节已经见过了——那个名字很直白的 object 类(以及它的派生类)。它们可以用 handle 来构造。下面这个例子应该能让你更清楚:
object main_module = import("__main__");
object main_namespace = main_module.attr("__dict__");
object ignored = exec("result = 5 ** 2", main_namespace);
int five_squared = extract(main_namespace["result"]);
这里先获得 __main__ 模块的命名空间字典,然后把 5 ** 2 的结果赋给变量 result,最后从字典中读出并提取成整数。当然,更简单的做法是用 eval:
object result = eval("5 ** 2");
int five_squared = extract(result);
异常处理
如果在执行 Python 表达式时发生了异常,Boost.Python 会抛出 error_already_set 异常:
try
{
object result = eval("5/0");
// 执行永远不会到这一行:
int five_divided_by_zero = extract(result);
}
catch(error_already_set const &)
{
// 用某种方式处理异常
}
error_already_set 异常类本身不包含任何信息。想了解发生了什么 Python 异常,你得在 catch 块里使用 Python C API 的异常处理函数。最简单的做法是调用 PyErr_Print() 把异常回显打印到控制台,或者比较异常类型:
catch(error_already_set const &)
{
if (PyErr_ExceptionMatches(PyExc_ZeroDivisionError))
{
// 专门处理除零异常
}
else
{
// 把其他所有错误打印到 stderr
PyErr_Print();
}
}
