SWIG与Python(下篇)
Posted on 2011 under python学习 | No Comment过去的章节主要描述python包装的高级视角。包装的主要特点就是,类和结构体都是用python代理类实现的。这可以提供一个很自然的接口,允许SWIG支持很多高级特点,类如运算符重载。但是一些底层的细节被省略了,这一小节主要介绍代理类怎么工作的。
33.4.1 代理类
你有这么样的类:
class Foo {
public:
int x;
int spam(int);
…
他会被包装成:
Foo *new_Foo() {
return new Foo();
}
void delete_Foo(Foo *f) {
delete f;
}
int Foo_x_get(Foo *f) {
return f->x;
}
void Foo_x_set(Foo *f, int value) {
f->x = value;
}
int Foo_spam(Foo *f, int arg1) {
return f->spam(arg1);
}
这个wrapper可以低级扩展模块里被发现。(_example.so)
SWIG会使用这个wrapper来生成高级python代理类(浅类)
import _example
class Foo(object):
def __init__(self):
self.this = _example.new_Foo()
self.thisown = 1
def __del__(self):
if self.thisown:
_example.delete_Foo(self.this)
def spam(self,arg1):
return _example.Foo_spam(self.this,arg1)
x = property(_example.Foo_x_get, _example.Foo_x_set)
这个类仅仅拥有底层C++对象的指针(this),通过使用低级的代理配套函数来实现分发对象的变量和方法。从用户的角度来说,工作很正常。
>>> f = example.Foo()
>>> f.x = 3
>>> y = f.spam(5)
C++类被包装成python类实际上有很多优势,你可以为这个类增加新方法,你甚至还可以从他派生出一个新类!
33.4.2 内置类型
内置类型对于包装代码的性能提高帮助很大,为了理解代理类和内置类型的区别,我们看看两种情况下的对象。
当python使用代理类时,每一个包装对象都是一个纯的python类的实例。
class Foo(object):
def __init__(self):
self.this = _example.new_Foo()
self.thisown = 1
当创建一个Foo实例,调用new_Foo()会创建一个C++的Foo实例。
Python内置类型包含了一个C++实例,它叫做SwigPyObject,在python的Foo 对象里的this域存储了SwigPyObject实例。
所以python的Foo对象包含3部分:
1. python实例
2. SwigPyObject结构体的实例
3. C++Foo对象
33.4.2.1限制
33.4.2.2运算符重载—尽管使用吧!
33.4.3内存管理
33.6普通个性化特性
这是最后一节,展示了C++包装的最基本细节。如果你只给SWIG一个头文件,你会获得同样类似的接口。但是为了产生一个好单元,这么做是远远不够的。功能的一些特点会被扔掉,一些功能的接口会很糟糕。这一节描述了一些SWIG普通的特性,你可以使用它来提高扩展模块的接口。
33.6.1 C++帮助函数
有事你会创建一个函数,他省略了一些功能,例如:
void set_transform(Image *im, double m[4][4]);
python可以得到这个函数,但是成功调用它可是很不容易啊:
>>> a = [
... [1,0,0,0],
… [0,1,0,0],
… [0,0,1,0],
… [0,0,0,1]]
>>> set_transform(im,a)
Traceback (most recent call last):
File “<stdin>”, line 1, in ?
TypeError: Type error. Expected _p_a_4__double
现在的问题就是,没有一个合适方法来创建并操纵一个合适的double [4][4],为了达到这个目的,你可以自己写一些协助函数。只使用 %inline指令就可以了:
%inline %{
/* Note: double[4][4] is equivalent to a pointer to an array double (*)[4] */
double (*new_mat44())[4] {
return (double (*)[4]) malloc(16*sizeof(double));
}
void free_mat44(double (*x)[4]) {
free(x);
}
void mat44_set(double x[4][4], int i, int j, double v) {
x[i][j] = v;
}
double mat44_get(double x[4][4], int i, int j) {
return x[i][j];
}
%}
在python中,你可以这么用:
>>> a = new_mat44()
>>> mat44_set(a,0,0,1.0)
>>> mat44_set(a,1,1,1.0)
>>> mat44_set(a,2,2,1.0)
…
>>> set_transform(im,a)
>>>
不得不承认,这不是最好的方法,他虽然可以实现功能, 但是很难去实施。使用typemaps的话这些都不是问题。
33.6.2 增加额外的python代码
如果写C的帮主函数还不够的话,你也可以写python的代码。
void set_transform(Image *im, double x[4][4]);
…
/* Rewrite the high level interface to set_transform */
%pythoncode %{
def set_transform(im,x):
a = new_mat44()
for i in range(4):
for j in range(4):
mat44_set(a,i,j,x[i][j])
_example.set_transform(im,a)
free_mat44(a)
%}
这个,你就可以这么调用:
>>> a = [
... [1,0,0,0],
… [0,1,0,0],
… [0,0,1,0],
… [0,0,0,1]]
>>> set_transform(im,a)
>>>
这个,不得不承认,为了包装二维数组参数,整个方案是很特别的。并且,一个python列表和数值python数组不行吗?
33.7 小建议和小技术
虽然SWIG大部分都是自动的,一些包装问题的一些特点需要用户的手工输入。例如在处理字符串,二进制数据和数组时。
33.7.1输入和输出参数
一个问题就是C语言通过指针来传参:
void add(int x, int y, int *result) {
*result = x + y;
}
或者是:
int sub(int *x, int *y) {
return *x-*y;
}
最简单的方式是使用typemaps.i文件:
%module example
%include “typemaps.i”
void add(int, int, int *OUTPUT);
int sub(int *INPUT, int *INPUT);
python允许你传递简单的值:
>>> a = add(3,4)
>>> print a
7
>>> b = sub(7,4)
>>> print b
3
>>>
注意INPUT参数是允许整数值代替指针来传值,而OUTPUT参数创建一个返回结果。
如果你不想用INPUT和OUTPUT,你可以用%apply指令:
%module example
%include “typemaps.i”
%apply int *OUTPUT { int *result };
%apply int *INPUT { int *x, int *y};
void add(int x, int y, int *result);
int sub(int *x, int *y);
如果一个函数不改变一个参数:
void negate(int *x) {
*x = -(*x);
}
那么你就要这么使用:
%include “typemaps.i”
…
void negate(int *INOUT);
python中,变化的参数通过值返回:
>>> a = negate(3)
>>> print a
-3
>>>
这些特殊的typemap规则大部分被用在了返回参数超过1个结果的和函数上。
33.7.2 简单指针 (important)
如果你只是使用int*double*这样的简单指针,你不必使用typename.i,考虑下cpointer.i吧:
%module example
%include “cpointer.i”
%inline %{
extern void add(int x, int y, int *result);
%}
%pointer_functions(int, intp);
%pointer_functions(type,name),这是个宏,他会创建5个协助函数:create, destroy, copy, assign, and dereference a pointer:
int *new_intp();
int *copy_intp(int *x);
void delete_intp(int *x);
void intp_assign(int *x, int value);
int intp_value(int *x);
python中,你可以这么使用:
>>> result = new_intp()
>>> print result
_108fea8_p_int
>>> add(3,4,result)
>>> print intp_value(result)
7
>>>
如果你把%pointer_functions()换成了%pointer_class(type,name),接口就更像类的了:
>>> result = intp()
>>> add(3,4,result)
>>> print result.value()
7
33.7.3 无边界的C数组
C函数有时候希望数组以指针的形式传递:
int sumitems(int *first, int nitems) {
int i, sum = 0;
for (i = 0; i < nitems; i++) {
sum += first[i];
}
return sum;
}
第一个参数应该是一个数组,简单是方式时使用carrays.i库文件:
%include “carrays.i”
%array_class(int, intArray);
%array_class(type, name)是一个宏,为一个无边界数组对象创建了一个包装, 你可以通过int*或者是double*来传值:
>>> a = intArray(10000000) # Array of 10-million integers
>>> for i in xrange(10000): # Set some values
… a[i] = i
>>> sumitems(a,10000)
49995000
>>>
%array_class()包装的数组对象不包装特殊对象数组的指针,事实上,也没有任何边界检查。这个库创建的数组事实上是相当低级的,你不能迭代他们,也不能查询他的长度。不用说,这个方式不会满足所有需求,换句话说,这种低级的方式在创建buffer,二进制包的时候是极其有效的。
33.7.4 字符串处理
如果C字符串有一个参数是char*,那么你可以传递一个python的字符串给他:
// C
void foo(char *s);
# Python
>>> foo(“Hello”)
当python的字符串当做参数传递给C函数时,C函数受到了指向这一块区域的指针。因为python字符串的指针不可变,你要是改变值就是不合法的。事实上,这么做会导致python解释器崩溃。
如果你的程序改变了输入参数,或者是使用它作为返回值,考虑使用cstring.i库。
当函数返回char*, 他被假定为一个NULL终止的字符串,数据被拷被到一个python字符串中,然后返回。
如果你的程序需要处理二进制,你可以使用typemap,扩展你的python字符串到一个 pointer/length参数对:
%apply (char *STRING, int LENGTH) { (char *data, int size) };
…
int parity(char *data, int size, int initial);
python中你可以这么调用:
>>> parity(“e\x09ffss\x00\x00\x01\nx”, 0)
如果你要返回一个二进制数据,你可以使用cstring.i 或者是cdata.i库。