Самое простое решение для этого - реализовать __getitem__
и выдать исключение IndexError
для недопустимого индекса.
Я собрал пример этого, используя %extend
и %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
на 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;
}
}
Требования для итерации по контейнерам таковы, что контейнер должен реализовать __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__()
, который возвращал копию контейнера и next()
, аналогичный первый тип. Я не делал этого в своем первоначальном ответе, потому что думал, что это будет менее понятно, чем два разных типа - контейнер и итератор для этого контейнера)