说明
Python 有多种非常好用的数据类型,如 Numbers,String,List,Tuple,Dictionary 和 Set。在前面的示例中我们经常用到的 Numbers 和 String(它们的内容) 可以直接在 C++ 代码中使用,因为这两者也是 C++ 的数据类型(虽然实现上不同,但不妨碍两者通用)。但是其他类型的数据结构在 C++ 中并没有,那么当 Python 需要使用这些类型且与 C++ 代码有交互时,该如何处理呢?
在 Boost.Python 和 C++ 的观点中,这些 Pythonic 变量只是类对象的实例。Python 的每一个变量或方法都是一个 Python 对象,这些都具备各种属性,且 Python 赋予了这些对象这样或那样的内置方法,以使我们可以方便地使用它们。Boost.Python 库提供了一个名为 object 的类,它可以封装一个有效的 Python 对象并提供了一个与 Python 类似的接口。换言之,每一个 Python 对象可以作为 Boost.Python 的一个 object 类实例,同时 Boost.Python 提供了这个实例接近 Python 语法的操作的支持,这些支持主要以运算符重载的形式实现。比如:
Python C++ 说明
y = x.foo y = x.attr(“foo”); 获取 x 的属性值,通常是成员变量
x.foo = 1 x.attr(“foo”) = 1; 设置 x 的属性
y = x[z] y = x[z]; 列表/字典操作
x[z] = 1 x[z] = 1; 列表/字典操作
y = x[3:-1] y = x.slice(3,-1); 切片操作
y = x[3:] y = x.slice(3,_);
y = x[:-2] y = x.slice(_,-2);
z = x(1, y) z = x(1, y); 调用函数
z = x.f(1, y) z = x.attr(“f”)(1, y); 调用成员函数
not x !x 逻辑非
x and y x && y 逻辑与
Boost.Python 的目标之一是提供 C++ 和 Python 之间的双向映射,同时保持 Python 的感觉。 Boost.Python C++ 对象尽可能接近 Python。在上表的操作示例中,虽然不完全一致,但是 Boost.Python 尽量提供了符合 C++ 语法又与 Python 功能一致的功能。
上述 Python 数据类型,除 Set 外,Boost.Python 都将其视为一个类的实例。如何理解呢,比如一个 Python 变量 a = [1,2,3,4] 是一个 List 类型,那么在 Boost.Python 中 a 是一个 class list 实例。
Object 类是一个基类,提供了针对所有Python 对象的通用的操作方法,对于 Python 的常用的数据类型,Boost.Python 提供了基于 object 的与 Python 类型对应的派生类:
list
dict
tuple
str
long_
enum
注:目前不包含 Set 类型。
这些派生类与基类的关系如下图:
针对 Python 对象的封装,Boost.Python 提出了两个封装概念: ObjectWrapper 和 TypeWrapper,前者用于描述管理 Python 的对象,后者针对特定的 Python 对象进行优化和改进。
ObjectWrapper
ObjectWrapper 定义了两个概念,用于描述管理 Python 地向的类,并且旨在支持使用类似 Python 的语法。
ObjectWrapper 概念的模型将 object 作为公用基类并用于通过成员函数提供特殊的构造函数或其他功能。除非返回类型 R 本身是一个 TypeWrapper,否则它是形式的成员函数调用。
比如语句
x.some_function(a1, a2,...an)
等价于:
extract<R>(x.attr("some_function")(object(a1), object(a2),...object(an)))()
TypeWrapper
TypeWrapper 是对 ObjectWrapper 的改进,它与特定的 Python 类型 X 相关联。对于给定的 TypeWrapper T,有效的构造函数表达式为:
T(a1, a2,...an)
构建一个新的 T 对象,管理调用 X 的结果,参数对应于:
object(a1), object(a2),...object(an)
当用作封装 C++ 函数的参数或用作 extract<> 的模板参数时,只有关联 Python 类型的实例才会被视为匹配项。
警告
当返回类型为 TypeWrapper 时,特殊成员函数调用的结果是返回的对象可能与特定 Python 对象的类型不匹配,通常情况下这不是一个严重的问题;最坏的结果是在运行时检测到错误的时间会比其他情况稍晚一些。有关如何发生这种情况的示例,请注意 dict 成员函数 items 返回列表类型的对象。现在假设用户在 Python 中定义了这个 dict 子类:
>>> class mydict(dict):
... def items(self):
... return tuple(dict.items(self)) # return a tuple
由于 mydict 的实例是 dict 的实例,因此当用作包装 C++ 函数的参数时,boost::python::dict 可以接受 Python 类型 mydict 的对象。在这个对象上调用 items() 可以导致 boost::python::list 的实例,它实际上包含一个 Python 元组。随后尝试在此对象上使用列表方法(例如追加或任何其他变异操作)将引发与尝试从 Python 执行此操作时发生的相同异常。
Object 基类
Object 类是通用 Python 对象的封装类和其他 TypeWrapper 类的基类。object 有一个模板化的构造函数,可以使用与 call<> 的参数相同的底层机制将任何 C++ 对象转换为 Python。如果一个对象实例是在没有任何构造函数参数的情况下创建的,那么这个实例的值就是 None。
object 类封装 PyObject*。处理 PyObjects 的所有复杂性,例如管理引用计数,都由对象类处理。Boost.Python C++ 对象实际上可以从任何 C++ 对象显式构造。
先看一个简单的示例,这个 Python 代码片段:
def f(x, y):
if (y == 'foo'):
x[3:7] = 'bar'
else:
x.items += y(3, x)
return x
def getfunc():
return f;
可以通过以下方式使用 Boost.Python 工具在 C++ 中重写:
object f(object x, object y) {
if (y == "foo")
x.slice(3,7) = "bar";
else
x.attr("items") += y(3, x);
return x;
}
object getfunc() {
return object(f);
}
除了由于我们使用 C++ 编写代码而导致的外观差异之外,Python 编码人员应该立即明白外观和有熟悉的感觉。