当前位置:   article > 正文

Boost(9):使用Boost.Python实现 C++ 和 Python 混合编程_boost/python.hpp

boost/python.hpp

目录

说明

嵌入 Python

编写嵌入 Python 程序

 C++ 调用 Python 代码

导入 Python 模块

执行 Python 文件

操作 Python 对象

调用 Python 方法

处理异常

关于命名空间变量

handle 类详解

exec 详解

Call 函数族

Python C API

完整示例

参考链接


说明

Boost.Python 不仅可以提供将 C++ 代码封装成 Python 接口供 Python 程序调用的功能,反过来,也可以使用 Boost.Python 在 C++ 程序中调用 Python 方法。但是需要注意的是,截至当前版本的 Boost(1.79) 的这一功能还不完善,需要与 Python/C API 结合使用。官方说法是后续可能会完善,但是当前还不能独立使用。

先总结一下个人想法吧,总体看下来,Boost.Python 的嵌入 Python 方案其实现完全依靠调用 Python/C API 的内容,只是在后者基础之上做了一层封装,使得看起来更加便利了而已。对于开发者来说,如果仅仅为了实现嵌入 Python 的功能,没有必要使用 Boost 库,但是对于依赖 Boost 的软件项目来说,使用 Boost.Python 确实能简化不少流程,且能降低这方面出错的概率。

嵌入 Python

Boost.Python 包含两种模型 extending 和 embedding。前者是将 C++ 编写的库封装成 Python 接口时使用的,后者则用于在 C++ 程序中调用 Python 解释器作为库子例程,一般为现有应用程序添加脚本功能。两者的主要区别在于 C++ main() 函数的位置,分别在 Python 解释器可执行文件或其他程序中。

我们将在 C/C++ 代码调用的 Python 语句称为 Embedding Python。

注:关于 extending 和 embedding,我们不必太在意细节,对于软件开发人员来说,他们在使用上没有任何区别。通常能使用 Boost.Python 库参与编译,这两者都是可用的。

编写嵌入 Python 程序

使用 Boost.Python 编写嵌入 Python 程序一般遵循以下4个步骤:

  1. 包含头文件 <boost/python.hpp>
  2. 调用 Py_Initialize() 来初始化 Python 解释器并创建 __main__ 模块
  3. 调用其他 Python C API 来使用 Python 解释器
  4. 编写自己的业务代码

注:在当前为止的版本(1.79),我们需要手动调用 Py_Finalize() 来停止 Python 解释器,在将来的版本中会解决这个问题,但是在当前还是需要。

先看一个简单的示例:

  1. #include <boost/python.hpp>
  2. using namespace boost::python;
  3. int main( int argc, char ** argv ) {
  4. try {
  5. Py_Initialize();
  6. // 方法一: 使用 Python C API 打印
  7. /*
  8. object main_module((
  9. handle<>(borrowed(PyImport_AddModule("__main__")))));
  10. object main_namespace = main_module.attr("__dict__");
  11. handle<> ignored(( PyRun_String( "print(\"Hello, World\")",
  12. Py_file_input,
  13. main_namespace.ptr(),
  14. main_namespace.ptr() ) ));
  15. */
  16. // 方法二:使用 Boost.Python 接口打印
  17. object ignored = exec("print(\"Hello, World\")");
  18. Py_Finalize();
  19. } catch( error_already_set ) {
  20. PyErr_Print();
  21. }
  22. }

CMakeLists.txt

  1. set(MODULE_NAME embedding)
  2. add_executable(${MODULE_NAME} hello.cpp)
  3. target_link_libraries(${MODULE_NAME}
  4. ${Boost_LIBRARIES}
  5. ${PYTHON_LIBRARIES}
  6. )

在这个示例中实现了两种方法来打印 "Hello, World"。方法一主要使用 Python C API 的方法,二则使用 Boost.Python 提供的 exec 功能。两者实现上没什么区别,后者是也是用方法一实现的,是对 Python C API 的封装,但是提供了更完善的检查和引用计数的处理。所以,如果使用了 Boost.Python,建议使用方法二。一方面更简洁,另一方面也更安全。

我们知道 Python 中的对象是有引用计数的,虽然使用 Python C API 的 Python 对象也有引用计数,但两者还是有区别的,Python 的引用计数由 Python 自动管理,而 Python C API 的引用计数则要求用户手动管理。这就使得对引用计数的处理变得很麻烦,尤其当程序运行异常时尤其难以解决。针对这一问题,Boost.Python 提供了 handle 和 object 类模板来自动化管理引用计数。也就是上面的例子中的 handle<>, object 相关语句。

 C++ 调用 Python 代码

Boost.Python 提供了四个相关的函数来在 C++ 中调用 Python 代码:

  1. // eval计算给定的表达式并返回结果值
  2. object eval(str expression, object globals = object(), object locals = object())
  3. // exec执行 code 指定的 Python 代码段(通常是一组语句)
  4. object exec(str code, object globals = object(), object locals = object())
  5. // exec_statement 执行 string 指定的 Python 语句
  6. object exec_statement(str string, object global = object(), object local = object());
  7. // exec_file执行给定文件中包含的代码
  8. object exec_file(str filename, object globals = object(), object locals = object())

 注:上例中的 str 参数也可以使用 const char *代替。

eval(), exec()exec_statement() 三个接口的参数完全一致,应用场景也很接近。区别在于其内部实现里调用的 PyRun_String() 方法的 start 参数分别是 Py_eval_input, Py_file_input Py_single_input。详见:The Very High Level Layer

由于 exec_statement() 只能执行一行 Python 语句,而 exec() 可以执行一到多行,所以我建议尽量使用后者。

globals 和 locals 分别对应于 PyRun_String() 的同名参数, Boost.Python 在这里做成有默认值的可缺省的参数。

在 Python 中,全局和局部参数是以字典的形式保存的,即{arg_name:arg_value},在大多数情况下,我们可以使用 __main__ 模块来获取这些参数。比如:

  1. object main_module = import("__main__");
  2. object main_namespace = main_module.attr("__dict__");
  3. object ignored = exec("hello = file('hello.txt', 'w')\n"
  4. "hello.write('Hello world!')\n"
  5. "hello.close()",
  6. main_namespace);

这段代码会在当前目录创建一个名为 "hello.txt"的文件,其中包含 "Hello world!" 内容。

导入 Python 模块

通过 Boost.Python 导入 Python 模块有两种方式,先看一种简单的,直接将 Python 模块的操作直接作为 Python 语句的一部分,然后调用 exec() 来运行:

  1. object ignored = exec("import sys\n"
  2. "version = sys.version\n"
  3. "print(version)\n", main_namespace);
  4. ignored = exec("import sys\n"
  5. "encode = sys.getdefaultencoding()\n"
  6. "print(encode)\n", main_namespace);

这种方式简单,但是缺点也显而易见,对模块的操作全都集中在一段代码内还好,但如果操作需要分段进行,则每次都要重新导入模块,效率太低。

一种更灵活的方式是,导入一次模块后,作为一个 C++ 对象保留下来,在任意需要的地方随时调用。Boost.Python 提供的 import() 属性可以很好的用于这种场景,导入后的模块的方法和变量等都作为 C++ 对象的属性。该 import 语句用于导入 Python 模块的调用格式为: 

object import(str name)

name 表示要导入的 python 模块名称。

那么上面的示例就可以改写为以下内容:

  1. object sys = import("sys");
  2. object version_o = sys.attr("version");
  3. std::string version_s = extract<std::string>(version_o);
  4. std::cout << version_s << std::endl;
  5. std::string encode = extract<std::string>(sys.attr("getdefaultencoding")());
  6. std::cout << encode << std::endl;

注: import() 函数是 Boost.Python 对 PyImport_ImportModule() 函数的封装,其作用与 object main_module((

handle<>(borrowed(PyImport_AddModule("__main__"))))); 语句类似。建议直接使用 import()。

执行 Python 文件

在 C++ 中执行 Python 程序文件,直接使用 exec_file() 即可,本身没什么可说的。但是需要注意两点。

先看示例:

  1. # embedding.py
  2. number_x = 30
  3. print ("This is embedded python file")
  4. if __name__ == '__main__':
  5. number_y = 20
  6. print ("Py run itself.")
  1. // embedding.cpp
  2. int main( int argc, char ** argv )
  3. {
  4. try {
  5. Py_Initialize();
  6. dict global;
  7. object result = exec_file("embedding.py", global, global);
  8. int num_x = extract<int>(global["number_x"]);
  9. // int num_y = extract<int>(global["number_y"]); // KeyError: 'number_y'
  10. std::cout << num_x << std::endl;
  11. Py_Finalize();
  12. } catch( error_already_set ) {
  13. PyErr_Print();
  14. }
  15. }

 编译后运行结果:

  1. This is embedded python file
  2. 30

 需要注意的两点是:

  1. 虽然是执行 Python 文件,但仍然是以 module 的形式。.py 文件中, if __name__ == '__main__': 以下的内容都不会执行(当然,这是有前提的,后面会说明);
  2. .py 文件中设置的变量会被保存到 Boost.Python 字典类型的命名空间中。

操作 Python 对象

我在之前的文章提到过,Boost.Python 使用类来抽象 Python 对象,恰当地使用 object 类及其派生类可以完美地操作所有的 Python 对象。

以下示例用以说明这一事实:

  1. object main_module = import("__main__");
  2. object main_namespace = main_module.attr("__dict__");
  3. object ignored = exec("result = 5 ** 2", main_namespace);
  4. int five_squared = extract<int>(main_namespace["result"]);

在这里,我们为 __main__ 模块的命名空间创建一个字典对象。然后我们将 5 平方分配给结果变量并从字典中读取该变量。实现相同结果的另一种方法是使用 eval 代替,它直接返回结果:

  1. object result = eval("5 ** 2");
  2. int five_squared = extract<int>(result);

接下来我们回到上一个程序,对 C++ 代码稍作修改:

  1. object main_module = import("__main__");
  2. object main_namespace = main_module.attr("__dict__");
  3. // dict global;
  4. object result = exec_file("embedding.py", main_namespace, main_namespace);
  5. int num_x = extract<int>(main_namespace["number_x"]);
  6. int num_y = extract<int>(main_namespace["number_y"]); // KeyError: 'number_y'
  7. std::cout << num_x << num_y << std::endl;

再次编译运行,结果如下:

  1. This is embedded python file
  2. Py run itself.
  3. 3020

和之前的程序相比,我们这一次在代码里导入了 __main__ 模块,__main__ 是 Python 程序的入口,我们在这里导入了 "__main__" 之后,Python 魔法函数 __name__ 就被赋值了 __main__,那么 .py 文件里的语句也就会被执行了。

调用 Python 方法

在上面这几种方式之外,C++ 程序还可以运行由 Python 作为参数传递进来的方法,此时 C++ 和 Python 之间的交互较为频繁,先由 Python 调用 C++ 接口,再在 C++ 内运行 Python 方法。

具体内容参见:boost.python:在c++程序中调用python的函数

OK,上面的文章的示例是将 python 方法作为一个 boost::python::object 实例传递到 C++ 函数内。除此之外,Boost.Python 还支持另外一种在 C++ 中回调 Python 方法的方法,这个需要用到函数模板 call。
call 函数的使用格式如下:

call<ResultType>(callable_object, a1, a2... aN);

ResultType 是 Python 方法的返回类型会转换成 C++ 的类型, callable_object 是 python 方法名称, a1 - aN 表示 python 方法的参数。

看一个示例,首先在 python 源文件中定义一个方法:

  1. def add_python(a, b):
  2. print("Add in python:", a+b)
  3. return a+b

内容很简单,不需要多说。然后在 cpp 文件中写一个函数来调用该方法: 

  1. int add_c (PyObject *func, int a, int b)
  2. {
  3. return python::call<int>(func, a, b);
  4. }

将 C++ 函数暴露给 python: 

def ("Add_c", add_c, return_value_policy<return_by_value>());

最后在 python 程序中调用暴露出来的 C++ 函数: 

cp.Add_c(add_python, 2, 3)

这样也可以实现在 C++ 程序中调用 python 方法。是不是觉得有点扯,其实大可不必这么麻烦对不对?我也这样认为,但是作为学习,多了解一种方法也无不可,而且结合其他方法一起用,会有意想不到的效果。

处理异常

如果在执行 Python 代码的过程当中发生异常,Boost.Python 会抛出 error_already_set 类,该类包含用于管理和转换 Python 和 C++ 异常的类型和函数。

  1. try
  2. {
  3. object result = eval("5/0");
  4. // execution will never get here:
  5. int five_divided_by_zero = extract<int>(result);
  6. }
  7. catch(error_already_set const &)
  8. {
  9. // handle the exception in some way
  10. }

error_already_set 异常类本身不携带任何信息。要了解有关发生的 Python 异常的更多信息,您需要在 catch 语句中使用 Python C API 的异常处理函数。这可以像调用 PyErr_Print() 将异常的回溯打印到控制台一样简单,或者将异常的类型与标准异常的类型进行比较:

  1. catch(error_already_set const &)
  2. {
  3. if (PyErr_ExceptionMatches(PyExc_ZeroDivisionError))
  4. {
  5. // handle ZeroDivisionError specially
  6. }
  7. else
  8. {
  9. // print all other errors to stderr
  10. PyErr_Print();
  11. }
  12. }

关于命名空间变量

嵌入 Python 是为了为我所用,而在前面的示例中,看似 C++ 调用了 Python 代码,但两者仍然是比较孤立的,C++ 内定义的一系列函数和变量等并不能为 Python 代码所使用,这样也起不到结合两者有点的作用。此时,命名空间就有文章可做了。

我们在调用 exec() 时传入了 main_space 作为全局变量,它是一个字典类型的变量,对应于 python 环境下的全局变量字典。格式为 ["name":object] ,也就是说几乎所有类型的 Python 对象都可以作为该变量的成员。当我们在 C++ 程序中初始化 Python 解释器后,main_space 下的成员可以作为 Python 目标的属性直接调用。

比如,我们在 C++ 下定义一个简单的类和函数:

  1. class mainspace {
  2. public:
  3. int get_num() { return 10; }
  4. };
  5. void showtime()
  6. {
  7. std::cout << "show time for namespace" << std::endl;
  8. }

然后在执行 Python 时将上述类和函数作为成员添加进 Python 全局变量:

  1. object main_module = import("__main__");
  2. object main_namespace = main_module.attr("__dict__");
  3. main_namespace["mainspace"] = class_<mainspace>("mainspace")
  4. .def("get_num",&mainspace::get_num);
  5. main_namespace["show_time"] = showtime;
  6. object ignored = exec("ms = mainspace()\n"
  7. "print(ms.get_num())\n"
  8. "show_time()\n", main_namespace);

此时 Python 全局变量下有两个成员: "mainspace" : class mainspace"show_time" : showtime()。 Python 可以直接使用这两个成员,运行结果如下:

  1. $ ./embedding
  2. 10
  3. show time for namespace

OK,前面我们是将类的定义传递到了 Python 空间,在 Python 运行时要先创建一个类实例,然后才能调用类的成员函数。如果在初始化 Python 解释器之前,我们就有一个类的实例,也可以直接将该实例引用到 Python 空间。

比如,先实例化类,再将该实例赋值给 main_space:

  1. mainspace aaa;
  2. ...
  3. main_namespace["mspace"] = ptr(&aaa);

然后在 Python 下就可以直接使用了:

object ignored = exec("print(mspace.get_num())\n", main_namespace);

于是,我们终于可以访问我们的 C++ 应用程序的数据了,但是所有的数据都在全局命名空间中,对于优秀的程序设计而言并不是一个好的做法。为了避免这种情况并提供适当的封装,我们应该将所有类和数据放入一个模块中。第一步是删除所有修改 main_namespace 对象的行,然后添加一个标准的 Boost 模块定义:

  1. BOOST_PYTHON_MODULE(CppMod) {
  2. class_<mainspace>("mainspace")
  3. .def("get_num",&mainspace::get_num);
  4. }

现在,在 main() 内部和调用 Py_Initialize() 之前,我们要调用 PyImport_AppendInittab( "CppMod", &initCppMod ); initCppMod 是由 BOOST_PYTHON_MODULE 宏创建的函数,用于初始化 Python 模块 CppMod。此时,我们的嵌入式 python 程序就可以调用 import CppMod,然后作为模块成员访问 mainspace。

handle 类详解

前面的内容提到,Boost.Python 使用 handle<> 来实现引用计数的自动管理。句柄本质上是一个指向 Python 对象类型的智能指针;它拥有一个 T* 类型的指针,其中 T 是它的模板参数。 T 必须是派生自 PyObject 的类型或初始 sizeof(PyObject) 字节与 PyObject 布局兼容的 POD 类型。在 Python/'C' API 和高级代码之间的边界处使用 handle<>;首选对象作为 Python 对象的通用接口。

handle 类定义在<boost/python/handle.hpp> 文件中,提供类模板句柄,一个用于管理引用计数的 Python 对象的智能指针。

  1. namespace boost { namespace python
  2. {
  3. template <class T>
  4. class handle
  5. {
  6. typedef T* (handle::* bool_type )() const;
  7. public: // types
  8. typedef T element_type;
  9. public: // member functions
  10. ~handle();
  11. template <class Y>
  12. explicit handle(detail::borrowed<null_ok<Y> >* p);
  13. template <class Y>
  14. explicit handle(null_ok<detail::borrowed<Y> >* p);
  15. template <class Y>
  16. explicit handle(detail::borrowed<Y>* p);
  17. template <class Y>
  18. explicit handle(null_ok<Y>* p);
  19. template <class Y>
  20. explicit handle(Y* p);
  21. handle();
  22. template <typename Y>
  23. handle(handle<Y> const& r);
  24. handle(handle const& r);
  25. T* operator-> () const;
  26. T& operator* () const;
  27. handle& operator=(handle const& r);
  28. template<typename Y>
  29. handle& operator=(handle<Y> const & r); // never throws
  30. T* get() const; // 返回当前句柄
  31. void reset(); // 重置handle类对象,此时句柄指向的地址为NULL
  32. T* release(); // 返回当前句柄,然后将当前句柄清零
  33. operator bool_type() const; // never throws
  34. private:
  35. T* m_p;
  36. };
  37. template <class T> struct null_ok;
  38. namespace detail { template <class T> struct borrowed; }
  39. }}

handle 的构造函数和析构函数实现上依赖于 Python/C API 的 Py_XINCREF() Py_XDECREF() 等操作,用于实现引用计数的加1减1操作。

构造函数的使用格式如下:

  1. // 创建一个引用计数 +1 的 NULL 指针 x
  2. handle<> x;
  3. handle<> x = handle<> ();
  4. // 创建一个基于对象 y 的带有引用计数的指针, null_ok 表示 y 可以为 NULL
  5. handle<> x(y);
  6. handle<> x(null_ok(y));
  7. handle<> x = handle<> (y);
  8. handle<> x = handle<> (null_ok(y));
  9. // 创建一个与原有对象 y 的引用计数相同的的智能指针x,borrowed()表示对象是“借用”的,保持引用计数一致
  10. handle<> x(borrowed(y));
  11. handle<> x(null_ok(borrowed(y)));
  12. handle<> x = handle<> (borrowed(y));
  13. handle<> x = handle<> (null_ok(borrowed(y)));

null_ok 和 borrowed 的位置可以互换。

exec 详解

对于大部分的软件工程师来说,了解 exec() 等函数接口就足够了,这部分内容没必要了解。但是学习其具体实现有助于理解 Boost.Python 的实现逻辑,也有助于提升自己的编程能力,所以在这里分析一下。

  1. object BOOST_PYTHON_DECL exec(str string, object global, object local)
  2. {
  3. return exec(python::extract<char const *>(string), global, local);
  4. }
  5. object BOOST_PYTHON_DECL exec(char const *string, object global, object local)
  6. {
  7. // Set suitable default values for global and local dicts.
  8. if (global.is_none())
  9. {
  10. if (PyObject *g = PyEval_GetGlobals())
  11. global = object(detail::borrowed_reference(g));
  12. else
  13. global = dict();
  14. }
  15. if (local.is_none()) local = global;
  16. // should be 'char const *' but older python versions don't use 'const' yet.
  17. char *s = const_cast<char *>(string);
  18. PyObject* result = PyRun_String(s, Py_file_input, global.ptr(), local.ptr());
  19. if (!result) throw_error_already_set();
  20. return object(detail::new_reference(result));
  21. }

在没有指定全局和局部变量的情况下,Boost.Python 默认会借助 PyEval_GetGlobals() 来获取当前执行帧中全局变量的字典。如果失败则定义一个新的字典变量。但是头文件中将 global 和 local 设置了默认参数 dict(),所以一般情况下不会运行到 PyEval_GetGlobals()

调用 Python 的过程也是借助了 PyRun_String() 方法,只是在此基础上增加了异常处理。

eval() exec_statement() 的实现与 exec() 一样,只是在调用 PyRun_String() 的参数 Py_file_input 不同。

exec_file() 的实现由稍许不同,在此不做分析。

Call 函数族

在需要调用 Python 方法而又没有将 Python 对象传递到 C++ 环境里的情况下,Boost.Python 提供了两个函数模板族,call 和 call_method,分别用于在 PyObject *s 上调用 Python 函数和方法。 分别定义在 <boost/python/call.hpp> 和 <boost/python/call_method.hpp> 头文件中。调用格式如下:

  1. call<ResultType>(callable_object, a1, a2... aN);
  2. call_method<ResultType>(self_object, "method-name", a1, a2... aN);

 两者的区别在于,前者用于调用 Python 的独立函数,后者用于调用 Python 类对象的方法。

call_method 的示例如下:

C++:

  1. #include <boost/python/module.hpp>
  2. #include <boost/python/class.hpp>
  3. #include <boost/python/call_method.hpp>
  4. #include <boost/python/def.hpp>
  5. #include <boost/utility.hpp>
  6. #include <cstring>
  7. class Base_callback
  8. {
  9. public:
  10. Base_callback(PyObject* self) : m_self(self) {}
  11. char const* class_name() const { return python::call_method<char const*>(m_self, "class_name"); }
  12. private:
  13. PyObject* const m_self;
  14. };
  15. bool is_base(Base_callback *b)
  16. {
  17. std::cout << "Enter into is_base" << b->class_name() << std::endl;
  18. return !std::strcmp(b->class_name(), "Base");
  19. }
  20. BOOST_PYTHON_MODULE(callpython)
  21. {
  22. using namespace boost::python;
  23. def("is_base", is_base);
  24. class_<Base_callback, boost::noncopyable>("Base", init<PyObject*>())
  25. .def("class_name", &Base_callback::class_name)
  26. ;
  27. };

Python:

  1. # C++ 程序编译生成的库名称为 callpython.so
  2. import callpython as cp
  3. class Derived():
  4. def __init__(self,):
  5. None
  6. def class_name(self):
  7. print("Derived class name:", self.__class__.__name__)
  8. return self.__class__.__name__
  9. if __name__ == '__main__':
  10. ListTest()
  11. add_python(1, 2)
  12. cp.Add_c(add_python, 2, 3)
  13. der = Derived()
  14. base = cp.Base(der)
  15. cp.is_base(base)
  16. print(cp.is_base(base))
  17. base.class_name()

 这个示例是根据官网的示例改动的,链接为:boost/python/call_method.hpp - 1.79.0

如果直接编译官网示例运行时,会报如下错误:

ReferenceError: Attempt to return dangling pointer to object of type: char

在这种情况下,由 call_method 调用的 Python 方法会构造一个新的 Python 对象(该 Python 对象包含 C++ 对象)。而我尝试返回该 Python 对象拥有的 C++ 对象的引用。因为被调用的方法返回了一个全新的对象,也就是 class_name() 返回的引用。但是当函数返回时,Python 对象将被销毁,同时该对象的 class_name() 产生的对象也会被销毁,于是该方法返回的引用被悬空。此时如果再对该引用执行任何操作,都将导致程序崩溃并且抛出异常。

Python C API

Python/C API 是 Python 官方提供的使用 C 或 C++ 扩展 Python 的方法。这种方法需要以特定的方式来编写 C 代码以供 Python 去调用它。所有的 Python 对象都被表示为一种叫做 PyObject 的结构体,并且 Python.h 头文件中提供了各种操作它的函数。大部分对 Python 原生对象的基础函数和操作在 Python.h 头文件中都能找到。

详细教程见:Python/C API Reference Manualhttps://docs.python.org/3/c-api/index.html

完整示例

以下内容来自于 Boost 源代码,从文章完整性的角度供参考,其中有一些暂时还没有看到的内容,后面会补充文章进行说明。

  1. // Copyright Stefan Seefeld 2005.
  2. // Distributed under the Boost Software License, Version 1.0. (See
  3. // accompanying file LICENSE_1_0.txt or copy at
  4. // http://www.boost.org/LICENSE_1_0.txt)
  5. #include <boost/python.hpp>
  6. #include <boost/detail/lightweight_test.hpp>
  7. #include <iostream>
  8. namespace python = boost::python;
  9. // An abstract base class
  10. class Base : public boost::noncopyable
  11. {
  12. public:
  13. virtual ~Base() {};
  14. virtual std::string hello() = 0;
  15. };
  16. // C++ derived class
  17. class CppDerived : public Base
  18. {
  19. public:
  20. virtual ~CppDerived() {}
  21. virtual std::string hello() { return "Hello from C++!";}
  22. };
  23. // Familiar Boost.Python wrapper class for Base
  24. struct BaseWrap : Base, python::wrapper<Base>
  25. {
  26. virtual std::string hello()
  27. {
  28. #if BOOST_WORKAROUND(BOOST_MSVC, <= 1300)
  29. // workaround for VC++ 6.x or 7.0, see
  30. // http://boost.org/libs/python/doc/tutorial/doc/html/python/exposing.html#python.class_virtual_functions
  31. return python::call<std::string>(this->get_override("hello").ptr());
  32. #else
  33. return this->get_override("hello")();
  34. #endif
  35. }
  36. };
  37. // Pack the Base class wrapper into a module
  38. BOOST_PYTHON_MODULE(embedded_hello)
  39. {
  40. python::class_<BaseWrap, boost::noncopyable> base("Base");
  41. }
  42. void exec_test()
  43. {
  44. std::cout << "registering extension module embedded_hello..." << std::endl;
  45. // Register the module with the interpreter
  46. if (PyImport_AppendInittab("embedded_hello", initembedded_hello) == -1)
  47. throw std::runtime_error("Failed to add embedded_hello to the interpreter's "
  48. "builtin modules");
  49. std::cout << "defining Python class derived from Base..." << std::endl;
  50. // Retrieve the main module
  51. python::object main = python::import("__main__");
  52. // Retrieve the main module's namespace
  53. python::object global(main.attr("__dict__"));
  54. // Define the derived class in Python.
  55. python::object result = python::exec(
  56. "from embedded_hello import * \n"
  57. "class PythonDerived(Base): \n"
  58. " def hello(self): \n"
  59. " return 'Hello from Python!' \n",
  60. global, global);
  61. python::object PythonDerived = global["PythonDerived"];
  62. // Creating and using instances of the C++ class is as easy as always.
  63. CppDerived cpp;
  64. BOOST_TEST(cpp.hello() == "Hello from C++!");
  65. std::cout << "testing derived class from C++..." << std::endl;
  66. // But now creating and using instances of the Python class is almost
  67. // as easy!
  68. python::object py_base = PythonDerived();
  69. Base& py = python::extract<Base&>(py_base) BOOST_EXTRACT_WORKAROUND;
  70. // Make sure the right 'hello' method is called.
  71. BOOST_TEST(py.hello() == "Hello from Python!");
  72. std::cout << "success!" << std::endl;
  73. }
  74. void exec_file_test(std::string const &script)
  75. {
  76. std::cout << "running file " << script << "..." << std::endl;
  77. // Run a python script in an empty environment.
  78. python::dict global;
  79. python::object result = python::exec_file(script.c_str(), global, global);
  80. // Extract an object the script stored in the global dictionary.
  81. BOOST_TEST(python::extract<int>(global["number"]) == 42);
  82. std::cout << "success!" << std::endl;
  83. }
  84. void exec_test_error()
  85. {
  86. std::cout << "intentionally causing a python exception..." << std::endl;
  87. // Execute a statement that raises a python exception.
  88. python::dict global;
  89. python::object result = python::exec("print unknown \n", global, global);
  90. std::cout << "Oops! This statement should be skipped due to an exception" << std::endl;
  91. }
  92. int main(int argc, char **argv)
  93. {
  94. BOOST_TEST(argc == 2);
  95. std::string script = argv[1];
  96. // Initialize the interpreter
  97. Py_Initialize();
  98. bool error_expected = false;
  99. if (
  100. python::handle_exception(exec_test)
  101. || python::handle_exception(boost::bind(exec_file_test, script))
  102. || (
  103. (error_expected = true)
  104. && python::handle_exception(exec_test_error)
  105. )
  106. )
  107. {
  108. if (PyErr_Occurred())
  109. {
  110. if (!error_expected)
  111. BOOST_ERROR("Python Error detected");
  112. PyErr_Print();
  113. }
  114. else
  115. {
  116. BOOST_ERROR("A C++ exception was thrown for which "
  117. "there was no exception translator registered.");
  118. }
  119. }
  120. // Boost.Python doesn't support Py_Finalize yet, so don't call it!
  121. return boost::report_errors();
  122. }

参考链接

boost.python/EmbeddingPython - Python Wiki

Embedding - 1.79.0

boost/python/errors.hpp - 1.79.0

boost/python/handle.hpp - 1.79.0

boost.python/handle - Python Wiki

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/500480
推荐阅读
相关标签
  

闽ICP备14008679号