Giải pháp đơn giản nhất để điều này là triển khai __getitem__
và ném một ngoại lệ IndexError
cho chỉ mục không hợp lệ.
Tôi đặt cùng một ví dụ của việc này, sử dụng %extend
và %exception
trong SWIG để thực hiện __getitem__
và nâng cao một ngoại lệ tương ứng:
%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];
}
}
Tôi đã thử nghiệm nó bằng cách thêm vào 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;
}
Và chạy Python sau:
import test
for i in test.test():
print i
Bản in nào:
python run.py
0.0
1.0
2.0
3.0
4.0
5.0
6.0
7.0
8.0
9.0
và sau đó kết thúc.
Một cách tiếp cận khác, sử dụng một typemap để lập bản đồ MyStruct
lên một PyList
trực tiếp có thể quá:
%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"
này sẽ tạo ra một PyList
với giá trị trả về từ bất kỳ hàm trả về một MyStruct *
. Tôi đã thử nghiệm này %typemap(out)
với chức năng chính xác giống như phương pháp trước đó.
Bạn cũng có thể viết một ứng %typemap(in)
và %typemap(freearg)
cho điều ngược lại, một cái gì đó như mã chưa được kiểm tra này:
%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);
}
Sử dụng một iterator sẽ có ý nghĩa hơn đối với container như danh sách liên kết, nhưng cho đầy đủ vì lợi ích đây là cách bạn có thể thực hiện nó cho MyStruct
với __iter__
. Điều quan trọng là bạn lấy SWIG để bọc một loại khác cho bạn, cung cấp __iter__()
và next()
cần thiết, trong trường hợp này là MyStructIter
được xác định và gói cùng một lúc bằng cách sử dụng %inline
vì nó không phải là một phần của API C bình thường:
%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;
}
}
các yêu cầu cho iteration over containers là như vậy mà container cần phải thực hiện __iter__()
và trả về một iterator mới, nhưng ngoài next()
mà trả về mục tiếp theo và gia số iterator iterator bản thân cũng phải cung cấp một phương pháp __iter__()
. Điều này có nghĩa là một trong hai container hoặc một iterator có thể được sử dụng giống hệt nhau.
MyStructIter
cần theo dõi trạng thái lặp hiện tại - vị trí của chúng tôi và số lượng chúng ta còn lại. Trong ví dụ này, tôi đã làm điều đó bằng cách giữ một con trỏ đến mục tiếp theo và một bộ đếm mà chúng ta sử dụng để nói khi nào chúng ta nhấn vào cuối. Bạn cũng có thể lưu giữ theo dõi của sate bằng cách giữ một con trỏ đến MyStruct
iterator đang sử dụng và truy cập một cho vị trí trong đó, một cái gì đó như:
%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;
}
}
(Trong trường hợp này chúng ta có thể thực sự vừa sử dụng tự đóng gói làm trình vòng lặp làm trình lặp, bằng cách cung cấp một số __iter__()
trả lại một bản sao của vùng chứa và next()
tương tự như loại đầu tiên.Tôi đã không làm điều đó trong câu trả lời ban đầu của tôi bởi vì tôi nghĩ rằng sẽ ít rõ ràng hơn có hai loại riêng biệt - một vùng chứa và một trình lặp cho vùng chứa đó)
Cảm ơn các đoạn trích. Câu hỏi nhanh, bạn đã thử nghiệm điều này từ đầu Python chưa ?. Tôi không biết nhiều về typemaps và PyList/PyTuple nhưng tôi nghi ngờ rằng chúng gần gũi hơn với những gì tôi muốn đạt được (tức là xử lý cấu trúc C như một kiểu chuỗi Python). Tuy nhiên, điểm chính là vấn đề chính của tôi là tôi không thể lặp qua các phần tử trong cấu trúc C. có lẽ phơi bày cấu trúc C dưới dạng danh sách hoặc bộ túp giúp giết hai con chim (kiểm tra vòng lặp và mảng). Đó là hai vấn đề tôi đang cố gắng giải quyết - là typemap + PyList con đường phía trước ?. Bạn nghĩ sao? –
@HomunculusReticulli - Tôi đã thử nghiệm tất cả mọi thứ từ phía Python, sử dụng hàm 'test()' mà tôi đã chỉ ra, mặc dù đối với các typemaps, tôi chỉ thử nghiệm '% typemap (out)', nhưng cả hai phương thức đều làm việc tốt với 'for i trong ... 'Xây dựng Python. – Flexo
@HomunculusReticulli - Tôi cũng đã thêm biến '__iter __()' vào câu trả lời của mình mặc dù tôi thích phiên bản '__getitem __()' hơn trong trường hợp này. – Flexo