Python是动态类型的,与静态类型的C ++不同。 Python变量可以包含整数,浮点数,列表,字典,元组,str,long等等。 从Boost.Python和C ++的角度来看,这些Pythonic变量只是类object
实例。 我们将在本章中看到如何处理Python对象。
如前所述,Boost.Python的目标之一是在保持Python感觉的同时提供C ++和Python之间的双向映射。 Boost.Python C ++ object
尽可能接近Python。 这应该显着减少学习曲线。
类object
包装PyObject*
。 处理PyObject
的所有复杂性如管理引用计数都由object
类处理。 C ++对象的互操作性是无缝的。 事实上,Boost.Python C ++ object
可以从任何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编码器来说很明显。
Boost.Python附带了一组与Python相对应的派生object
类型:
这些派生object
类型就像真正的Python类型一样。 例如:
str(1) ==> "1"
在适当的情况下,特定的派生object
具有相应的Python类型的方法。 例如, dict
有一个keys()
方法:
d.keys()
make_tuple
用于声明元组文字 。 例:
make_tuple(123, 'D', "Hello, World", 0.0);
在C ++中,当Boost.Python object
用作函数的参数时,需要子类型匹配。 例如,当包含一个函数f
(如下所述)时,它只接受Python的str
类型和子类型的实例。
void f(str name)
{
object n2 = name.attr("upper")(); // NAME = name.upper()
str NAME = name.upper(); // better
object msg = "%s is bigger than %s" % make_tuple(NAME,name);
}
更精细的细节:
str NAME = name.upper();
说明我们提供str类型方法的版本作为C ++成员函数。
object msg = "%s is bigger than %s" % make_tuple(NAME,name);
证明你可以在Python中编写C ++等效的"format" % x,y,z
,这很有用,因为在std C ++中没有简单的方法可以做到这一点。
请注意忘记Python的大多数可变类型的构造函数都在复制中的常见缺陷,就像在Python中一样。
python:
>>> d = dict(x.__dict__) # copies x.__dict__
>>> d['whatever'] = 3 # modifies the copy
C ++:
dict d(x.attr("__dict__")); // copies x.__dict__
d['whatever'] = 3; // modifies the copy
由于Boost.Python对象的动态特性,任何class_
也可能是这些类型中的一种! 以下代码片段包装了类(类型)对象。
我们可以使用它来创建包装实例。 例:
object vec345 = (
class_("Vec2", init())
.def_readonly("length", &Point::length)
.def_readonly("angle", &Point::angle)
)(3.0, 4.0);
assert(vec345.attr("length") == 5.0);
在某些时候,我们需要从对象实例中获取C ++值。 这可以通过extract
功能来实现。 考虑以下:
double x = o.attr("length"); //编译错误
在上面的代码中,我们得到了编译器错误,因为Boost.Python object
不能隐式转换为double
s。 相反,我们想要做的就是通过写:
double l = extract(o.attr("length"));
Vec2& v = extract(o);
assert(l == v.length());
第一行尝试提取Boost.Python object
的“length”属性。 第二行尝试从Boost.Python object
提取 Vec2
object
。
请注意我们上面说“尝试”。 如果Boost.Python object
实际上不包含Vec2
类型怎么办? 考虑到Python object
的动态特性,这当然是一种可能性。 为安全起见,如果无法提取C ++类型,则会引发相应的异常。 为避免异常,我们需要测试可提取性:
extract x(o);
if (x.check()) {
Vec2& v = x(); ...
精明的读者可能已经注意到, extract
工具实际上解决了可变复制问题:
dict d = extract(x.attr("__dict__"));
d["whatever"] = 3; // modifies x.__dict__ !
Boost.Python有一个很好的工具来捕获和包装C ++枚举。 虽然Python没有enum
类型,但我们经常希望将我们的C ++枚举作为int
公开给Python。 Boost.Python的枚举工具使得这很简单,同时处理从Python的动态类型到C ++的强静态类型的正确转换(在C ++中,整数不能隐式转换为枚举)。 为了说明,给定一个C ++枚举:
enum choice { red, blue };
构造:
enum_("choice")
.value("red", red)
.value("blue", blue)
;
可以用来暴露给Python。 新的枚举类型是在当前scope()
创建的,该scope()
通常是当前模块。 上面的代码片段创建了一个派生自Python的int
类型的Python类,它与作为第一个参数传递的C ++类型相关联。
注意 | |
---|---|
什么是范围? 范围是一个具有关联的全局Python对象的类,该对象控制Python命名空间,其中新的扩展类和包装函数将被定义为属性。 细节可以在这里找到。 |
您可以在Python中访问这些值
>>> my_module.choice.red
my_module.choice.red
其中my_module是声明枚举的模块。 您还可以围绕类创建新范围:
scope in_X = class_("X")
.def( ... )
.def( ... )
;
// Expose X::nested as X.nested
enum_("nested")
.value("red", red)
.value("blue", blue)
;
当你想要一个boost :: python :: object
来管理一个指向PyObject *
pyobj的指针时,你会这样做:
boost :: python :: object o ( boost :: python :: handle <>( pyobj ));
在这种情况下, o
对象管理pyobj
,它不会增加构造的引用计数。
否则,使用借来的参考:
boost :: python :: object o ( boost :: python :: handle <>( boost :: python :: borrowed ( pyobj )));
在这种情况下, Py_INCREF
,因此当对象o超出范围时,不会破坏pyobj
。