SWIG接口的C库到Python(从创建C'序'迭代'Python数据类型结构) [英] SWIG interfacing C library to Python (Creating 'iterable' Python data type from C 'sequence' struct)

查看:139
本文介绍了SWIG接口的C库到Python(从创建C'序'迭代'Python数据类型结构)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经写了一个C库Python扩展。我有一个数据结构,看起来像这样:

I have written a Python extension for a C library. I have a data structure that looks like this:

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

此数据类型的目的直接映射到在Python列表中的数据类型。所以我要为出口结构打造列表类似的行为,使得code。使用我的C扩展更Python化写的。

The purpose of this datatype maps directly to the list data type in Python. I therefore, want to create 'list-like' behavior for the exported struct, so that code written using my C extension is more 'Pythonic'.

在特定的,这就是我希望能够做的(从蟒蛇code)
注:py_ctsruct是蟒蛇被访问的数据类型ctsruct

In particular, this is what I want to be able to do (from python code) Note: py_ctsruct is a ctsruct datatype being accessed in python.

我的要求可sumarized为:

My requirements can be sumarized as:


  1. 列表(py_ctsruct)返回与C结构
  2. 复制所有内容的Python列表
  3. py_cstruct [I]返回 第i 元素(preferably抛出IndexError无效指数)

  4. 在py_ctsruct ELEM:能否列举

  1. list(py_ctsruct) returns a python list with all contents copied out from the c struct
  2. py_cstruct[i] returns ith element (preferably throws IndexError on invalid index)
  3. for elem in py_ctsruct: ability to enumerate

根据 PEP234 的对象可以遍历与供,如果它实现
   _ _()的 ITER 的或_ 的GetItem 的_()
。根据这种逻辑的话,我认为,通过添加以下属性(通过重命名)我痛饮接口文件,我会期望的行为(除了上面的REQ#1 - 我仍然不知道如何实现):

According to PEP234, An object can be iterated over with "for" if it implements _iter_() or _getitem_(). Using that logic then, I think that by adding the following attributes (via rename) to my SWIG interface file, I will have the desired behavior (apart from req. #1 above - which I still dont know how to achieve):

__len__
__getitem__
__setitem__

我现在能够索引在Python中C目标。我还没有实现Python的异常抛出,但是如果数组边界超出,正在返回一个神奇的数字(错误code)。

I am now able to index the C object in python. I have not yet implemented the Python exception throwing, however if array bounds are exceeded, are return a magic number (error code).

有趣的是,当我试图使用结构遍历对于x的'语法,例如:

The interesting thing is that when I attempt to iterate over the struct using 'for x in' syntax for example:

for i in py_cstruct:
    print i

的Python进入一个无限循环,简单的打印上面提到的魔法(错误)的数量,在控制台上。这表明,我认为有什么不对的索引。

Python enters into an infinite loop that simply prints the magic (error) number mentioned above, on the console. which suggests to me that there is something wrong with the indexing.

最后但并非最不重要的,我怎么能实现要求1吗?这涉及(据我所知):

last but not the least, how can I implement requirement 1? this involves (as I understand it):


  • 处理'的函数调用的列表()从蟒蛇

  • 从C code返回一个Python(名单)数据类型

[更新]

我会希望看到一个小code片段是什么(如果有的话)我需要把我的接口文件,这样我就可以在C结构的元素,Python的迭代声明。

I would be interested in seeing a little code snippet on what (if any) declarations I need to put in my interface file, so that I can iterate over the elements of the c struct, from Python.

推荐答案

解决这个问题的最简单的解决方案是实现<一个href=\"http://docs.python.org/reference/datamodel.html#object.__getitem__\"><$c$c>__getitem__并抛出一个<一个href=\"http://docs.python.org/library/exceptions.html#exceptions.IndexError\"><$c$c>IndexError例外无效的索引。

The simplest solution to this is to implement __getitem__ and throw an IndexError exception for an invalid index.

我放在一起这样的一个例子,使用%延长%的异常在痛饮实施 __ __的GetItem 分别产生一个异常:

I put together an example of this, using %extend and %exception in SWIG to implement __getitem__ and raise an exception respectively:

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

I tested it by adding to 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:

And running the following 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 直接也是可能的:

An alternative approach, using a typemap to map MyStruct onto a PyList directly is possible too:

%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 * 。我测试了这个%类型映射(出)通过精确相同的功能previous方法。

This will create a PyList with the return value from any function that returns a MyStruct *. I tested this %typemap(out) with the exact same function as the previous method.

您还可以编写一个相应的%类型映射(中)%类型映射(freearg)为反向,像这样未经检验code:

You can also write a corresponding %typemap(in) and %typemap(freearg) for the reverse, something like this untested code:

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


使用迭代器将使像链表容器更有意义,但出于完整性起见这里是你会如何去与这样做是为了 MYSTRUCT __iter __ 。关键的一点是,你痛饮包装另一种类型适合你,它提供了 __ ITER __()的next()需要,在这种情况下 MyStructIter 这是使用定义并包裹在同一时间%直列,因为它不是一部分正常的C API:


Using an iterator would make more sense for containers like linked lists, but for completeness sake here's how you might go about doing it for MyStruct with __iter__. The key bit is that you get SWIG to wrap another type for you, which provides the __iter__() and next() needed, in this case MyStructIter which is defined and wrapped at the same time using %inline since it's not part of the normal 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;
  }
}

有关迭代超过容器是这样的容器需要实现的要求 __ ITER __(),并返回一个新的迭代,但除了的next()返回的下一个项目,并增加迭代的迭代器本身也必须提供一个 __ __国际热核实验堆()方法。这意味着,无论是容器或迭代器可以使用相同的

The requirements for iteration over containers are such that the container needs to implement __iter__() and return a new iterator, but in addition to next() which returns the next item and increments the iterator the iterator itself must also supply a __iter__() method. This means that either the container or an iterator can be used identically.

MyStructIter 需要不断迭代的当前状态的轨迹 - 我们在哪里,有多少我们已经离开了。在这个例子中,我这样做,通过保持一个指向下一个项目,而我们用它来告诉我们打到最后一个计数器。你也可以藉由保持一个指针保持跟踪沙爹的 MYSTRUCT 正在使用的迭代器和内的位置计数器,是这样的:

MyStructIter needs to keep track of the current state of iteration - where we are and how much we have left. In this example I did that by keeping a pointer to the next item and a counter that we use to tell when we hit the end. You could also have kept track of the sate by keeping a pointer to the MyStruct the iterator is using and a counter for the position within that, something like:

%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 __()对返回的复制<容器和/ em>的一个的next()类似于第一种类型,我没有这样做,在我原来的答复,因为我认为那会比有不太清楚两种不同类型的 - 一个容器和该容器的迭代器)

(In this instance we could actually have just used the container itself as the iterator as an iterator, by supplying an __iter__() that returned a copy of the container and a next() similar to the first type. I didn't do that in my original answer because I thought that would be less clear than have two distinct types - a container and an iterator for that container)

这篇关于SWIG接口的C库到Python(从创建C'序'迭代'Python数据类型结构)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆