对此最简单的解决方案是实现__getitem__ 并针对无效索引抛出IndexError 异常。
我整理了一个例子,在 SWIG 中使用 %extend 和 %exception 来实现 __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 直接映射到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,其返回值来自任何返回MyStruct * 的函数。我用与之前方法完全相同的功能测试了这个%typemap(out)。
你也可以反过来写一个对应的%typemap(in)和%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 和__iter__ 执行此操作的方法。关键是您让 SWIG 为您包装另一种类型,它提供所需的 __iter__() 和 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;
}
}
iteration over containers 的要求是容器需要实现 __iter__() 并返回一个新的迭代器,但除了 next() 返回下一项并递增迭代器之外,迭代器本身还必须提供一个 @ 987654351@ 方法。这意味着容器或迭代器可以相同地使用。
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__()和类似于以下内容的next()第一种类型。我在最初的答案中没有这样做,因为我认为这比拥有两种不同的类型(容器和该容器的迭代器)更清晰)