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

Hom*_*lli 7 c python swig

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

typedef struct _mystruct{
   double * clientdata;
   size_t   len;
} MyStruct;
Run Code Online (Sandbox Code Playgroud)

此数据类型的目的直接映射到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__
Run Code Online (Sandbox Code Playgroud)

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

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

for i in py_cstruct:
    print i
Run Code Online (Sandbox Code Playgroud)

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

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

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

[[更新]]

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

Fle*_*exo 17

最简单的解决方案是为无效索引实现__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];
  }
}
Run Code Online (Sandbox Code Playgroud)

我通过添加到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;
}
Run Code Online (Sandbox Code Playgroud)

并运行以下Python:

import test

for i in test.test():
  print i
Run Code Online (Sandbox Code Playgroud)

哪个印刷品:

python run.py
0.0
1.0
2.0
3.0
4.0
5.0
6.0
7.0
8.0
9.0
Run Code Online (Sandbox Code Playgroud)

然后完成.


使用类型映射直接映射MyStruct到a 的替代方法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"
Run Code Online (Sandbox Code Playgroud)

这将创建一个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);
}
Run Code Online (Sandbox Code Playgroud)

使用迭代器对链接列表之类的容器更有意义,但为了完整起见,这里是你如何进行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;
  }
}
Run Code Online (Sandbox Code Playgroud)

迭代容器的要求是容器需要实现__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;
  }
}
Run Code Online (Sandbox Code Playgroud)

(在这个例子中,我们实际上可以使用容器本身作为迭代器作为迭代器,通过提供一个__iter__()返回容器的副本next()类似于第一种类型.我在原来的答案中没有这样做因为我认为不如两个不同的类型 - 容器和该容器的迭代器那么清晰