SWIG将C库接口到Python(从C'sequence'结构创建'iterable'Python数据类型)

2024-09-29 00:22:48 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

这个数据类型的用途直接映射到Python中的list数据类型。因此,我想为导出的结构创建“list-like”行为,以便使用C扩展编写的代码更加“python”。在

特别是,这是我希望能够做到的(从python代码) 注意:py_ctsruct是在python中访问的ctsruct数据类型。在

我的要求可以概括为:

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

根据PEP234如果一个对象实现了 _iter或<getitem。使用这个逻辑,我认为通过将以下属性(通过rename)添加到我的SWIG接口文件中,我将获得所需的行为(除了req。#1以上-我仍然不知道如何实现):

^{pr2}$

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

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

for i in py_cstruct:
    print i

Python进入一个无限循环,在控制台上简单地打印上面提到的神奇(错误)数字。这对我来说意味着索引有问题。在

最后但并非最不重要的是,我如何实现需求1?这包括(据我所知):

  • 处理来自python的函数调用list()
  • 从C代码返回Python(list)数据类型

[[更新]]

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


Tags: 文件对象代码inpy元素数据结构for
3条回答

最简单的解决方案是实现^{}并对无效索引抛出一个^{}异常。在

我举了一个例子,在SWIG中使用%extend和{}来实现__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:

^{pr2}$

并运行以下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

然后结束。在


另一种方法是,使用typemap将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。关键是让SWIG为您包装另一个类型,它提供所需的__iter__()和{},在本例中,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;
  }
}

iteration over containers的要求是容器需要实现__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()。在我最初的回答中我没有这样做,因为我认为这比有两种不同的类型(容器和容器的迭代器)更不清楚

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

所以实际上,使用SWIG异常处理是相当容易的。我只需要添加以下魔法:

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

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

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

^{pr2}$

python3.x注意事项:

您应该用神奇的“yu”前缀和后缀名来命名next()函数。一种选择是简单地添加:

%rename(__next__) MyStructIter::next;
  1. 使用%typemap swig命令查找。http://www.swig.org/Doc2.0/SWIGDocumentation.html#Typemaps 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++中。我想也会有类似的功能。在
  2. 另外,您可以在swig“i”文件内的结构内部的接口中定义%pythoncode。这将允许您在为结构创建的对象中添加python方法。还有另一个命令%addmethod(我想)允许您向结构或类中添加方法。然后,如果需要,可以创建用于在C++或C中索引对象的方法。有很多方法可以解决这个问题。在

对于我正在处理的接口,我使用了一个类对象,它有一些方法来访问代码中的数据。这些方法是用C++编写的。然后,在“I”文件内部的类中使用了%python代码指令,并在Python代码中创建了“<强> GestEng/St>”和“<强> SeTiTEM>强”>方法,使用了EngultC++方法使其看起来像字典式访问。在

相关问题 更多 >