说明
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))
由于 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 编码人员应该立即明白外观和有熟悉的感觉。
Object 类提供了诸如切片,属性策略和操作符等功能的应用,详细内容参见:boost/python/object.hpp
Object 派生类
Object 派生类也就是前面提到的 TypeWrapper 的行为类似于真正的 Python 类型。例如:
str(1) ==> "1"
在适当的情况下,特定的派生对象具有相应的 Python 类型的方法。例如, dict 有一个 keys() 方法:
d.keys()
make_tuple 用于声明元组。例子:
make_tuple(123, 'D', "Hello, World", 0.0);
在 C++ 中,当 Boost.Python 对象用作函数的参数时,需要进行子类型匹配。例如,当如下声明的函数 f 被包装时,它将只接受 Python 的 str 类型和子类型的实例。
void f(str name)
{
object n2 = name.attr("upper")();
str NAME = name.upper();
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 中编写与 “format” % x,y,z 等效的 C++,这很有用,因为在标准 C++ 中没有简单的方法可以做到这一点。
但是需要注意的是:大多数情况下,Python 和 C++ 之间的对象是拷贝过来的,如果修改某一个对象不会影响到另一个对象的值。
# Python:
>>> d = dict(x.__dict__) # copies x.__dict__
>>> d['whatever'] = 3 # modifies the copy
dict d(x.attr("__dict__"));
d['whatever'] = 3;
class_ as objects
由于 Boost.Python 对象的动态特性,任何 class_ 也可能是这些类型之一!我们可以使用它来创建包装实例。例子:
object vec345 = (
class_<Vec2>("Vec2", init<double, double>())
.def_readonly("length", &Point::length)
.def_readonly("angle", &Point::angle)
)(3.0, 4.0);
assert(vec345.attr("length") == 5.0);
Creating boost::python::object from PyObject*
当我们希望 boost::python::object 管理指向 PyObject* pyobj 的指针时,可以:
boost::python::object o(boost::python::handle<>(pyobj));
在这种情况下,o 对象管理 pyobj,它不会增加构造时的引用计数。
否则,要参考使用 boost::python::borrowed :
boost::python::object o(boost::python::handle<>(boost::python::borrowed(pyobj)));
在这种情况下,调用 Py_INCREF,因此当对象 o 超出范围时,pyobj 不会被破坏。
list class
暴露 Python 内置的列表类型的方法。下面定义的构造函数和成员函数的语义可以通过阅读 TypeWrapper 概念定义来完全理解。由于 list 是从 object 公开派生的,因此 public object 接口也适用于 list 实例。
该类定义在 <boost/python/list.hpp> 头文件中,部分代码 如下:
namespace boost { namespace python
{
class list : public object
{
public:
list();
template <class T>
explicit list(T const& sequence);
template <class T>
void append(T const& x);
template <class T>
long count(T const& value) const;
template <class T>
void extend(T const& x);
template <class T>
long index(T const& x) const;
template <class T>
void insert(object const& index, T const& x);
object pop();
object pop(long index);
object pop(object const& index);
template <class T>
void remove(T const& value);
void reverse();
void sort();
template <class T>
void sort(T const& value);
};
}}
Python list 支持的大部分方法在 Boost.Python list class 中都有实现,且为了尽量与 python 的习惯一致,函数调用方式和参数也都类似。
示例 下面以简单的 reverse() 函数为例,介绍 Boost.Python 的 list 类的使用 C++:
#include <iostream>
#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
#include <boost/python/return_by_value.hpp>
#include <boost/python/return_value_policy.hpp>
#include <boost/python/list.hpp>
namespace bp = boost::python;
bp::list list_reverse (bp::list &l1)
{
std::cout << "Input list length: " << len(l1) << std::endl;
l1.reverse();
return l1;
}
BOOST_PYTHON_MODULE(objects)
{
using namespace boost::python;
def("list_reverse", list_reverse, args("l1"), return_value_policy<return_by_value>())
;
}
Python:
$ python
>>> import objects
>>> a = [1, 3, 6, 2, 9, 0]
>>> objects.list_reverse(a)
Input list length: 6
[0, 9, 2, 6, 3, 1]
好吧,这个示例很无聊,主要目的是说明当 python 接口传入一个 list 参数时,C++ 可以通过 Boost.Python 实现对该参数的处理。
dict class
dict 类是针对 python 字典数据类型封装的 object 衍生类,提供了 python dict 类似的方法和操作。定义在文件 <boost/python/dict.hpp> 中。
namespace boost { namespace python
{
class dict : public object
{
dict();
template< class T >
dict(T const & data);
void clear();
dict copy();
template <class T1, class T2>
tuple popitem();
template <class T>
object setdefault(T const &k);
template <class T1, class T2>
object setdefault(T1 const & k, T2 const & d);
void update(object_cref E);
template< class T >
void update(T const & E);
list values() const;
object get(object_cref k) const;
template<class T>
object get(T const & k) const;
object get(object_cref k, object_cref d) const;
object get(T1 const & k, T2 const & d) const;
bool has_key(object_cref k) const;
template< class T >
bool has_key(T const & k) const;
list items() const;
object iteritems() const;
object iterkeys() const;
object itervalues() const;
list keys() const;
};
}}
示例 C++:
bp::dict swap_object_dict(bp::object target, bp::dict d)
{
bp::dict result = bp::extract<bp::dict>(target.attr("__dict__"));
target.attr("__dict__") = d;
return result;
}
这个函数会将输入参数中的字典 d 的内容和 target 的 __dict__ 属性互换,并将 target 的原有 __dict__ 属性作为新的字典输出。
Python:
import objects
class O1:
''' class O1 '''
Name = 'Test'
Age = 0
def __init__(self):
self.Name = 'Test'
self.Age = 0
a = O1()
print(a.__dict__)
d1 = {"Name":"Xiangdi", "Age":10}
print(d1)
d2 = objects.swap_object_dict(a, d1)
print(a.__dict__)
print(d2)
运行结果:
$ python objects.py
{'Na': 'Test', 'Ag': 0}
{'Name': 'Xiangdi', 'Age': 10}
{'Name': 'Xiangdi', 'Age': 10}
{'Na': 'Test', 'Ag': 0}
tuple class
这个类封装了部分类似 Python tuple 数据类型的功能。但是相对于其他衍生类, class tuple 的函数相对较少,只有一个构造函数。特点在于提供了多种构造 tuple 的重载函数 make_tuple。其定义在 <boost/python/tuple.hpp> 中。
namespace boost { namespace python
{
class tuple : public object
{
tuple();
template <class T>
explicit tuple(T const& sequence)
};
}}
namespace boost { namespace python
{
tuple make_tuple();
template <class A0>
tuple make_tuple(A0 const& a0);
template <class A0, class A1>
tuple make_tuple(A0 const& a0, A1 const& a1);
...
template <class A0, class A1,...class An>
tuple make_tuple(A0 const& a0, A1 const& a1,...An const& an);
}}
示例 C++:
bp::tuple head_and_tail_tuple (bp::object seq)
{
return bp::make_tuple(seq[0],seq[-1]);
}
Python:
$ python
>>> import objects
>>> t1 = ("Ali", "Baidu", "Tencent", "Google")
>>> t2 = objects.head_and_tail_tuple(t1)
>>> print(t2)
('Ali', 'Google')
str class
str class 模拟了 Python 内置 str 类型的字符串方法。定义在 <boost/python/str.hpp> 中,其提供的函数如下:
namespace boost { namespace python
{
class str : public object
{
public:
str();
str(char const* s);
str(char const* start, char const* finish);
str(char const* start, std::size_t length);
template <class T>
explicit str(T const& other);
str capitalize() const;
template <class T>
str center(T const& width) const;
template<class T>
long count(T const& sub) const;
template<class T1, class T2>
long count(T1 const& sub,T2 const& start) const;
template<class T1, class T2, class T3>
long count(T1 const& sub,T2 const& start, T3 const& end) const;
object decode() const;
template<class T>
object decode(T const& encoding) const;
template<class T1, class T2>
object decode(T1 const& encoding, T2 const& errors) const;
object encode() const;
template <class T>
object encode(T const& encoding) const;
template <class T1, class T2>
object encode(T1 const& encoding, T2 const& errors) const;
template <class T>
bool endswith(T const& suffix) const;
template <class T1, class T2>
bool endswith(T1 const& suffix, T2 const& start) const;
template <class T1, class T2, class T3>
bool endswith(T1 const& suffix, T2 const& start, T3 const& end) const;
str expandtabs() const;
template <class T>
str expandtabs(T const& tabsize) const;
template <class T>
long find(T const& sub) const;
template <class T1, class T2>
long find(T1 const& sub, T2 const& start) const;
template <class T1, class T2, class T3>
long find(T1 const& sub, T2 const& start, T3 const& end) const;
template <class T>
long index(T const& sub) const;
template <class T1, class T2>
long index(T1 const& sub, T2 const& start) const;
template <class T1, class T2, class T3>
long index(T1 const& sub, T2 const& start, T3 const& end) const;
bool isalnum() const;
bool isalpha() const;
bool isdigit() const;
bool islower() const;
bool isspace() const;
bool istitle() const;
bool isupper() const;
template <class T>
str join(T const& sequence) const;
template <class T>
str ljust(T const& width) const;
str lower() const;
str lstrip() const;
template <class T1, class T2>
str replace(T1 const& old, T2 const& new_) const;
template <class T1, class T2, class T3>
str replace(T1 const& old, T2 const& new_, T3 const& maxsplit) const;
template <class T>
long rfind(T const& sub) const;
template <class T1, class T2>
long rfind(T1 const& sub, T2 const& start) const;
template <class T1, class T2, class T3>
long rfind(T1 const& sub, T2 const& start, T3 const& end) const;
template <class T>
long rindex(T const& sub) const;
template <class T1, class T2>
long rindex(T1 const& sub, T2 const& start) const;
template <class T1, class T2, class T3>
long rindex(T1 const& sub, T2 const& start, T3 const& end) const;
template <class T>
str rjust(T const& width) const;
str rstrip() const;
list split() const;
template <class T>
list split(T const& sep) const;
template <class T1, class T2>
list split(T1 const& sep, T2 const& maxsplit) const;
list splitlines() const;
template <class T>
list splitlines(T const& keepends) const;
template <class T>
bool startswith(T const& prefix) const;
template <class T1, class T2>
bool startswidth(T1 const& prefix, T2 const& start) const;
template <class T1, class T2, class T3>
bool startswidth(T1 const& prefix, T2 const& start, T3 const& end) const;
str strip() const;
str swapcase() const;
str title() const;
template <class T>
str translate(T const& table) const;
template <class T1, class T2>
str translate(T1 const& table, T2 const& deletechars) const;
str upper() const;
};
}}
从上面的定义中可以看出,Boost.Python str 类给出的函数与 Python 的原生方法是一一对应的,但是也要注意仍然没有 Python 原生方法灵活,比如有些函数缺乏部分参数,而且 zfill() 和 isdecimal() 等方法并没有包含进来。
示例 C++:
bp::str set_str_as_title (bp::str x)
{
return x.title();
}
Python:
$ python
>>> import objects
>>> s = "boost wrap str class"
>>> objects.set_str_as_title(s)
'Boost Wrap Str Class'
long class
对于数字,C++ 和 Python 基本上是可以通用的,而且 Python 的这一数据类型也没有什么额外的方法,从 long 的定义也没有看到有什么成员函数。<boost/python/long.hpp>
namespace boost { namespace python
{
class long_ : public object
{
public:
long_();
template <class T>
explicit long_(T const& rhs);
template <class T, class U>
long_(T const& rhs, U const& base);
};
}}
示例 对于 long class 类型和直接使用 int 在功能上有什么区别,到目前为止我还不是很了解。 看下面的示例:
namespace bp = boost::python;
int multile_int (int x)
{
return x * 2;
}
bp::long_ multile_long (bp::long_ x)
{
int y = bp::extract<int>(x);
bp::long_ z(y*2);
return z;
}
def("multile_int", multile_int, args("x"), return_value_policy<return_by_value>());
def("multile_long", multile_long, args("x"), return_value_policy<return_by_value>());
对于同样的 Python 参数,其返回值是一样的:
>>> import objects
>>> objects.multile_int(2)
4
>>> objects.multile_long(2)
4
但是需要注意的是在上例中 multile_int() 里的 x 以及返回值都是一个数值,在 C++ 程序里可以直接打印出来。而在 multile_long() 里的 z 是一个类的实例。虽然最后传递到 python 里会拷贝数值,但在 C++ 程序里是不能直接作为数值操作的。
enum class
虽然 Python 没有枚举类型,但是作为 C++ 的基本数据类型,在软件开发的过程中非常常见。在碰到这一类型时,我们经常希望将它作为 int 传递给 Python。 Boost.Python 同样也提供了枚举工具,同时负责从 Python 的动态类型到 C++ 的强静态类型的正确转换(在 C++ 中,int 不能隐式转换为枚举)。
这个类相关定义在 <boost/python/enum.hpp> 中。该头文件定义了用户向 Python 公开其 C++ 枚举类型的接口。其类定义如下:
namespace boost { namespace python
{
template <class T>
class enum_ : public object
{
enum_(char const* name, char const* doc = 0);
enum_<T>& value(char const* name, T x);
enum_<T>& export_values();
};
}}
创建一个派生自 Python 的 int 类型的 Python 类,该类型与作为其第一个参数传递的 C++ 类型相关联。
enum 的使用与其他派生类不同,为了说明,给定一个 C++ 枚举:
enum choice { red, blue };
需要指定构造函数并将名称和值绑定:
enum_<choice>("choice")
.value("red", red)
.value("blue", blue)
;
之后 choice 及其元素便可以暴露给 Python。我们可以在 Python 中访问这些值:
>>> my_module.choice.red
my_module.choice.red
其中 my_module 是声明枚举的模块。您还可以围绕一个类创建一个新范围:
scope in_X = class_<X>("X")
.def( ... )
.def( ... )
;
enum_<X::nested>("nested")
.value("red", red)
.value("blue", blue)
;
新的枚举类型是在当前的 scope() 中创建的,它通常是当前的模块。上面的代码片段创建了一个派生自 Python 的 int 类型的 Python 类,该类型与作为其第一个参数传递的 C++ 类型相关联。
注:scope是一个具有关联的全局 Python 对象的类,该对象控制 Python 命名空间,其中新的扩展类和包装的函数将被定义为属性。其具体说明后面更。
示例 C++:
#include <boost/python/enum.hpp>
#include <boost/python/def.hpp>
#include <boost/python/module.hpp>
using namespace boost::python;
enum color { red = 1, green = 2, blue = 4 };
color identity_(color x) { return x; }
BOOST_PYTHON_MODULE(enums)
{
enum_<color>("color")
.value("red", red)
.value("green", green)
.export_values()
.value("blue", blue)
;
def("identity", identity_);
}
Python:
>>> from enums import *
>>> identity(red)
enums.color.red
>>> identity(color.red)
enums.color.red
>>> identity(green)
enums.color.green
>>> identity(color.green)
enums.color.green
>>> identity(blue)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
NameError: name 'blue' is not defined
>>> identity(color.blue)
enums.color.blue
>>> identity(color(1))
enums.color.red
>>> identity(color(2))
enums.color.green
>>> identity(color(3))
enums.color(3)
>>> identity(color(4))
enums.color.blue
>>> identity(1)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: bad argument type for built-in operation
注意上面 Line15 和 Line3/9 的区别,在 Python 程序中可以直接使用 “red”, “green” 作为参数,而 “blue” 不行。这是因为在前面的构造函数中, export_values() 前面的 scope 只包含 “red” 和 “green” 两个值。
参考资料
ObjectWrapper Object Interface Chapter 3. Object Wrappers boost.python object boost.python list
|