SWIG将C库连接到Python(从C’序列’结构创建’可迭代’Python数据类型)

我为C库编写了一个Python扩展。 我有一个如下所示的数据结构:

typedef struct _mystruct{ double * clientdata; size_t len; } MyStruct; 

此数据类型的目的直接映射到Python中的列表数据类型。 因此,我想为导出的结构创建“类似列表”的行为,因此使用我的C扩展编写的代码更像是“Pythonic”。

特别是,这是我想要做的(来自python代码)注意:py_ctsruct是在python中访问的ctsruct数据类型。

我的要求可以归结为:

  1. list(py_ctsruct)返回一个python列表,其中包含从c结构中复制的所有内容
  2. py_cstruct [i]返回ith元素(最好在无效索引上抛出IndexError)
  3. 对于py_ctsruct中的elem:枚举的能力

根据PEP234 , 如果一个对象实现_ iter _()或_ getitem _()则可以使用“for”进行迭代 。 然后使用该逻辑,我认为通过将以下属性(通过重命名 )添加到我的SWIG接口文件,我将具有所需的行为(除了上面的请求#1 – 我仍然不知道如何实现):

 __len__ __getitem__ __setitem__ 

我现在能够在python中索引C对象。 我还没有实现Pythonexception抛出,但是如果超出数组边界,则返回一个幻数(错误代码)。

有趣的是,当我尝试使用’for x in’语法迭代结构时,例如:

 for i in py_cstruct: print i 

Python进入一个无限循环,只需在控制台上打印上面提到的魔术(错误)编号。 这告诉我索引有问题。

最后但并非最不重要,我如何实施要求1? 这涉及(据我了解):

  • 从python处理’函数调用列表()
  • 从C代码返回Python(列表)数据类型

[[更新]]

我有兴趣看一下我需要在我的接口文件中放入什么(如果有的话)声明的代码片段,以便我可以从Python迭代c结构的元素。

最简单的解决方案是实现__getitem__并为无效索引抛出IndexErrorexception。

我把一个例子放在一起,在SWIG中使用%extend%exception来实现__getitem__并分别引发exception:

 %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来测试它:

 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 

然后完成。


另一种方法是使用类型映射将PyList直接映射到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); } 

使用迭代器对链接列表等容器更有意义,但为了完整起见,您可以使用__iter__MyStruct执行此MyStruct 。 关键是你得到SWIG为你包装另一种类型,它提供所需的__iter__()next() ,在这种情况下MyStructIter使用%inline同时定义和包装,因为它不是正常的一部分C API:

 %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; } } 

迭代容器的要求是容器需要实现__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()类似于第一种类型。我没有这样做在我的原始答案中,因为我认为这比两个不同的类型 - 容器和该容器的迭代器更不清楚

  1. 使用%typemap swig命令查找。 http://www.swig.org/Doc2.0/SWIGDocumentation.html#Typemaps http://www.swig.org/Doc2.0/SWIGDocumentation.html#Typemaps_nn25 memberin typemap可能会执行您想要的操作。 http://www.swig.org/Doc2.0/SWIGDocumentation.html#Typemaps_nn35我在Python部分找到了一个类型映射,它允许我将char **数据作为Python字符串列表传输到C ++中。 我猜想会有类似的function。
  2. 此外,您可以在swig“i”文件中的struct内的接口中定义%pythoncode。 这将允许您在为结构创建的对象中添加python方法。 还有另一个命令%addmethod(我认为)允许您向结构或类添加方法。 然后,您可以创建用于在C ++或C中索引对象的方法(如果需要)。 有很多方法可以解决这个问题。

对于我正在处理的接口,我使用了一个类对象,它有一些方法可以访问我的代码中的数据。 这些方法是用C ++编写的。 然后我在“i”文件里面的类中使用%pythoncode指令,并在Python代码中创建了“ getitem ”和“ setitem ”方法,这些方法使用公开的C ++方法使其看起来像字典样式访问。

我在Python 2.6中遇到了同样的问题,并且感谢@aphex回复解决了这个问题。 但我想避免任何魔术值,或额外的布尔值来传递列表结束条件。 果然,我的迭代器有一个atEnd()方法告诉我我已经超过了列表的末尾。

事实上,SWIGexception处理相当容易。 我只需添加以下魔法:

 %ignore MyStructIter::atEnd(); %except MyStructIter::next { if( $self->list->atEnd() ) { PyErr_SetString(PyExc_StopIteration,"End of list"); SWIG_fail; } $action } 

关键是,一旦超过列表末尾,这个snipet将完全跳过next()调用。

如果你坚持你的习语,它应该看起来像:

 %except MyStructIter::next { if( $self->pos >= $self->list->len ) { PyErr_SetString(PyExc_StopIteration,"End of list"); SWIG_fail; } $action } 

PYTHON 3.x的注意事项:

您应使用魔术“__”前缀和后缀名称命名您的next()函数。 一个选项只是添加:

 %rename(__next__) MyStructIter::next; 

你说你还没有实现Pythonexception抛出 – 这就是问题所在。 从PEP 234:

定义了一个新的exceptionStopIteration,它可以用来表示迭代的结束。

您必须在迭代结束时设置此exception。 由于您的代码不会执行此操作,因此您遇到了您所描述的情况:

  1. 解释器循环遍历列表的自定义iternext函数
  2. 你的函数到达数组的末尾,而不是正确设置StopIterationexception,只需返回你的“幻数”。
  3. 看到没有充分理由停止迭代的解释器,只需继续打印iternext返回的值……你的幻数。 对于翻译,它只是另一个列表成员。

幸运的是,这是一个非常简单的修复,虽然可能看起来不那么简单,因为C没有exceptionfunction。 Python C API只使用在引发exception情况时设置的全局错误指示符,然后API标准规定您在堆栈中一直返回NULL到解释器,然后解释器查看PyErr_Occurred()的输出查看是否设置了错误,如果是,则打印相关的exception和回溯。

所以在你的函数中,当你到达数组的末尾时,你只需要:

 PyErr_SetString(PyExc_StopIteration,"End of list"); return NULL; 

以下是进一步阅读此问题的另一个很好的答案: 如何使用Python C API创建生成器/迭代器?