![](/img/trans.png)
[英]SWIG interfacing C library to Python (SWIG generated classes are cumbersome to use)
[英]SWIG interfacing C library to Python (Creating 'iterable' Python data type from C 'sequence' struct)
我为 C 库编写了一个 Python 扩展。 我有一个如下所示的数据结构:
typedef struct _mystruct{
double * clientdata;
size_t len;
} MyStruct;
此数据类型的用途直接映射到 Python 中的列表数据类型。 因此,我想为导出的结构创建“类似列表”的行为,以便使用我的 C 扩展编写的代码更加“Pythonic”。
特别是,这是我想要做的(从 python 代码) 注意:py_ctsruct 是在 python 中访问的 ctsruct 数据类型。
我的要求可以概括为:
根据PEP234 ,如果对象实现了 _ iter _() 或 _ getitem _() ,则可以使用“for”对其进行迭代。 然后使用该逻辑,我认为通过将以下属性(通过rename )添加到我的 SWIG 接口文件中,我将拥有所需的行为(除了上面的 req. #1 - 我仍然不知道如何实现):
__len__
__getitem__
__setitem__
我现在可以在 python 中索引 C 对象。 我还没有实现 Python 异常抛出,但是如果超出数组边界,则返回一个幻数(错误代码)。
有趣的是,当我尝试使用 'for x in' 语法遍历结构时,例如:
for i in py_cstruct:
print i
Python 进入一个无限循环,在控制台上简单地打印上面提到的魔法(错误)数字。 这向我表明索引有问题。
最后但并非最不重要的一点是,我如何实现要求 1? 这涉及(据我所知):
[[更新]]
我很想看到一些关于我需要在接口文件中放置什么(如果有)声明的代码片段,以便我可以从 Python 迭代 c 结构的元素。
最简单的解决方案是实现__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);
}
使用迭代器对于链表之类的容器更有意义,但为了完整起见,您可以使用__iter__
为MyStruct
执行此MyStruct
。 关键是你让 SWIG 为你包装另一种类型,它提供了__iter__()
和next()
,在这种情况下MyStructIter
使用%inline
同时定义和包装,因为它不是正常的一部分CAPI:
%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()
。我没有这样做在我原来的答案中,因为我认为这比有两种不同的类型更不清晰 - 一个容器和一个容器的迭代器)
我在Python 2.6 上遇到了同样的问题,感谢@aphex 回复解决了这个问题。 但我想避免任何魔法值或额外的布尔值来传递列表结束条件。 果然,我的迭代器有一个atEnd()方法,它告诉我我已经超过了列表的末尾。
所以实际上,使用 SWIG 异常处理相当容易。 我只需要添加以下魔法:
%ignore MyStructIter::atEnd();
%exception MyStructIter::next {
if( $self->list->atEnd() ) {
PyErr_SetString(PyExc_StopIteration,"End of list");
SWIG_fail;
}
$action
}
关键是一旦你超过列表的末尾,这个 snipet 就会完全跳过 next() 调用。
如果你坚持你的习惯用法,它应该是这样的:
%exception 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;
对于我正在处理的接口,我使用了一个类对象,该对象具有一些用于访问代码中数据的方法。 这些方法是用 C++ 编写的。 然后,我在“i”文件内的类中使用了 %pythoncode 指令,并在 Python 代码中创建了“ getitem ”和“ setitem ”方法,这些方法使用公开 C++ 方法使其看起来像字典样式的访问。
你说你还没有实现 Python 异常抛出 - 这就是问题所在。 来自 PEP 234:
定义了一个新的异常 StopIteration,它可用于表示迭代结束。
您必须在迭代结束时设置此异常。 由于您的代码没有执行此操作,因此您遇到了您所描述的情况:
iternext
函数StopIteration
异常,只是返回您的“幻数”。iternext
返回的值……你的幻数。 对于解释器来说,它只是另一个列表成员。 幸运的是,这是一个非常简单的修复,虽然看起来可能不那么简单,因为 C 没有异常功能。 Python C API 仅使用您在引发异常情况时设置的全局错误指示符,然后 API 标准规定您将 NULL 一直向上返回到解释器,然后解释器查看PyErr_Occurred()
的输出以查看是否设置了错误,如果是,则打印相关的异常和回溯。
所以在你的函数中,当你到达数组的末尾时,你只需要这样:
PyErr_SetString(PyExc_StopIteration,"End of list");
return NULL;
这是进一步阅读这个问题的另一个很好的答案: How to create a generator/iterator with the Python C API?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.