最简单的解决方案是实施__getitem__ http://docs.python.org/reference/datamodel.html#object.__getitem__并抛出一个IndexError http://docs.python.org/library/exceptions.html#exceptions.IndexError无效索引的异常。
我整理了一个例子,使用%extend
and %exception
在 SWIG 中实施__getitem__
并分别引发异常:
%module test
%include "exception.i"
%{
#include <assert.h>
#include "test.h"
static int myErr = 0; // flag to save error state
%}
%exception MyStruct::__getitem__ {
assert(!myErr);
$action
if (myErr) {
myErr = 0; // clear flag for next time
// You could also check the value in $result, but it's a PyObject here
SWIG_exception(SWIG_IndexError, "Index out of bounds");
}
}
%include "test.h"
%extend MyStruct {
double __getitem__(size_t i) {
if (i >= $self->len) {
myErr = 1;
return 0;
}
return $self->clientdata[i];
}
}
我通过添加到 test.h 来测试它:
static MyStruct *test() {
static MyStruct inst = {0,0};
if (!inst.clientdata) {
inst.len = 10;
inst.clientdata = malloc(sizeof(double)*inst.len);
for (size_t i = 0; i < inst.len; ++i) {
inst.clientdata[i] = i;
}
}
return &inst;
}
并运行以下 Python:
import test
for i in test.test():
print i
哪个打印:
python run.py
0.0
1.0
2.0
3.0
4.0
5.0
6.0
7.0
8.0
9.0
然后完成。
另一种方法,使用类型映射来映射MyStruct
onto a PyList
直接也可以:
%module test
%{
#include "test.h"
%}
%typemap(out) (MyStruct *) {
PyObject *list = PyList_New($1->len);
for (size_t i = 0; i < $1->len; ++i) {
PyList_SetItem(list, i, PyFloat_FromDouble($1->clientdata[i]));
}
$result = list;
}
%include "test.h"
这将创建一个PyList
与任何返回 a 的函数的返回值MyStruct *
。我测试了这个%typemap(out)
与之前的方法具有完全相同的功能。
也可以写一个对应的%typemap(in)
and %typemap(freearg)
相反,类似这样未经测试的代码:
%typemap(in) (MyStruct *) {
if (!PyList_Check($input)) {
SWIG_exception(SWIG_TypeError, "Expecting a PyList");
return NULL;
}
MyStruct *tmp = malloc(sizeof(MyStruct));
tmp->len = PyList_Size($input);
tmp->clientdata = malloc(sizeof(double) * tmp->len);
for (size_t i = 0; i < tmp->len; ++i) {
tmp->clientdata[i] = PyFloat_AsDouble(PyList_GetItem($input, i));
if (PyErr_Occured()) {
free(tmp->clientdata);
free(tmp);
SWIG_exception(SWIG_TypeError, "Expecting a double");
return NULL;
}
}
$1 = tmp;
}
%typemap(freearg) (MyStruct *) {
free($1->clientdata);
free($1);
}
使用迭代器对于像链表这样的容器来说更有意义,但为了完整性起见,您可能会这样做MyStruct
with __iter__
。关键是你让 SWIG 为你包装另一种类型,它提供了__iter__()
and next()
在这种情况下需要MyStructIter
使用以下方法同时定义和包装%inline
因为它不是普通 C API 的一部分:
%module test
%include "exception.i"
%{
#include <assert.h>
#include "test.h"
static int myErr = 0;
%}
%exception MyStructIter::next {
assert(!myErr);
$action
if (myErr) {
myErr = 0; // clear flag for next time
PyErr_SetString(PyExc_StopIteration, "End of iterator");
return NULL;
}
}
%inline %{
struct MyStructIter {
double *ptr;
size_t len;
};
%}
%include "test.h"
%extend MyStructIter {
struct MyStructIter *__iter__() {
return $self;
}
double next() {
if ($self->len--) {
return *$self->ptr++;
}
myErr = 1;
return 0;
}
}
%extend MyStruct {
struct MyStructIter __iter__() {
struct MyStructIter ret = { $self->clientdata, $self->len };
return ret;
}
}
要求容器迭代 http://docs.python.org/library/stdtypes.html#iterator-types是这样的容器需要实现__iter__()
并返回一个新的迭代器,但除了next()
它返回下一个项目并递增迭代器,迭代器本身还必须提供__iter__()
方法。这意味着容器或迭代器可以相同地使用。
MyStructIter
需要跟踪迭代的当前状态——我们在哪里以及还剩下多少。在这个例子中,我通过保留一个指向下一个项目的指针和一个计数器来实现这一点,我们用它来告诉我们何时到达末尾。您还可以通过保留指向的指针来跟踪状态MyStruct
迭代器正在使用一个计数器来表示其中的位置,例如:
%inline %{
struct MyStructIter {
MyStruct *list;
size_t pos;
};
%}
%include "test.h"
%extend MyStructIter {
struct MyStructIter *__iter__() {
return $self;
}
double next() {
if ($self->pos < $self->list->len) {
return $self->list->clientdata[$self->pos++];
}
myErr = 1;
return 0;
}
}
%extend MyStruct {
struct MyStructIter __iter__() {
struct MyStructIter ret = { $self, 0 };
return ret;
}
}
(在这种情况下,我们实际上可以将容器本身用作迭代器,通过提供__iter__()
返回了一个copy容器和一个next()
与第一种类似。我在最初的答案中没有这样做,因为我认为这比两种不同的类型更不清楚 - 容器和该容器的迭代器)