SWIG interfacing C library to Python (Creating 'iterable' Python data type from C 'sequence' struct)
我已经为C库编写了Python扩展。我有一个看起来像这样的数据结构:
1 2 3 4 | typedef struct _mystruct{ double * clientdata; size_t len; } MyStruct; |
此数据类型的目的直接映射到Python中的列表数据类型。因此,我想为导出的结构创建"类似列表"的行为,以便使用我的C扩展编写的代码更加" Pythonic"。
特别是,这就是我想要做的(来自python代码)
注意:py_ctsruct是在python中访问的ctsruct数据类型。
我的要求可以概括为:
根据PEP234,如果对象实现,则可以使用" for"进行迭代
_iter_()或_getitem_()。然后,使用该逻辑,我认为通过将以下属性(通过重命名)添加到我的SWIG接口文件中,我将具有所需的行为(除了上面的要求#1-我仍然不知道如何实现):
1 2 3 | __len__ __getitem__ __setitem__ |
我现在能够在python中索引C对象。我尚未实现Python异常抛出,但是如果超出数组范围,则会返回一个幻数(错误代码)。
有趣的是,当我尝试使用" for x in"语法对结构进行迭代时:
1 2 | for i in py_cstruct: print i |
Python进入一个无限循环,该循环只是在控制台上打印上述的魔术(错误)数字。这向我暗示索引存在问题。
最后但并非最不重要的一点,我该如何实施要求1?这涉及(据我了解):
- 从python处理'函数调用list()
- 从C代码返回Python(列表)数据类型
[[更新]]
我很想在我的接口文件中看到一些声明(如果有)的代码片段,以便可以从Python遍历c结构的元素。
最简单的解决方案是实现
我整理了一个示例,在SWIG中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | %module test %include"exception.i" %{ #include #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中进行了测试:
1 2 3 4 5 6 7 8 9 10 11 | 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:
1 2 3 4 | import test for i in test.test(): print i |
哪些打印:
1 2 3 4 5 6 7 8 9 10 11 | python run.py 0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 |
然后完成。
也可以使用一种类型映射将
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | %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" |
这将使用返回
您也可以为相反的内容编写相应的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | %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); } |
对于链表之类的容器,使用迭代器会更有意义,但是出于完整性考虑,这就是使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | %module test %include"exception.i" %{ #include #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; } } |
对容器进行迭代的要求是,容器需要实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | %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; } } |
(在这种情况下,我们实际上可以通过提供一个返回容器副本的
我在Python 2.6中遇到了同样的问题,并通过@aphex答复解决了它。
但是我想避免任何不可思议的值,或者避免额外的布尔值来传递列表结尾条件。果然,我的迭代器有一个atEnd()方法,告诉我我已经超出列表末尾了。
因此,实际上,使用SWIG异常处理非常容易。我只需要添加以下魔术:
1 2 3 4 5 6 7 8 | %ignore MyStructIter::atEnd(); %except MyStructIter::next { if( $self->list->atEnd() ) { PyErr_SetString(PyExc_StopIteration,"End of list"); SWIG_fail; } $action } |
关键是,一旦您超过列表的末尾,此摘要将完全跳过next()调用。
如果您遵循惯用语,则应如下所示:
1 2 3 4 5 6 7 | %except MyStructIter::next { if( $self->pos >= $self->list->len ) { PyErr_SetString(PyExc_StopIteration,"End of list"); SWIG_fail; } $action } |
PYTHON 3.x的注意事项:
您应使用神奇的" __"前缀和后缀名称来命名next()函数。一种选择是简单地添加:
1 | %rename(__next__) MyStructIter::next; |
http://www.swig.org/Doc2.0/SWIGDocumentation.html#Typemaps_nn25
memberin类型映射可能会执行您想要的操作。
http://www.swig.org/Doc2.0/SWIGDocumentation.html#Typemaps_nn35
我在Python部分找到了一个类型映射,该映射允许我将char **数据作为Python字符串列表传输到C ++中。我猜会有类似的功能。
对于一个接口,我正在使用一个类对象,该对象具有一些用于访问代码中数据的方法。这些方法是用C ++编写的。然后,我在" i"文件内部的类中使用了%pythoncode指令,并在Python代码中创建了" getitem"和" setitem"方法,这些方法使用暴露的C ++方法使其看起来像字典样式访问。
您说您尚未实现Python异常抛出-这就是问题。从PEP 234:
A new exception is defined, StopIteration, which can be used to signal the end of an iteration.
您必须在迭代结束时设置此异常。由于您的代码没有执行此操作,因此您遇到了上述情况:
幸运的是,这是一个非常简单的修复程序,尽管可能看起来并不简单,因为C没有异常功能。 Python C API仅使用引发异常情况时设置的全局错误指示符,然后API标准指示您从栈顶一直返回NULL到解释器,然后解释器将
因此,在函数中,当到达数组末尾时,您只需要这样做:
1 2 | PyErr_SetString(PyExc_StopIteration,"End of list"); return NULL; |
这是进一步阅读此问题的另一个好答案:如何使用Python C API创建生成器/迭代器?