繁体   English   中英

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

[英]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 数据类型。

我的要求可以概括为:

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

根据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 代码返回 Python(列表)数据类型

[[更新]]

我很想看到一些关于我需要在接口文件中放置什么(如果有)声明的代码片段,以便我可以从 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

然后完成。


另一种方法,使用一个类型映射来映射MyStructPyList直接也是可能的:

%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;
  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++ 中。 我猜会有类似的功能。
  2. 此外,您可以在 swig "i" 文件内的结构内的界面中定义 %pythoncode。 这将允许您在为结构创建的对象中添加 python 方法。 还有另一个命令 %addmethod (我认为)允许您向结构或类添加方法。 然后,您可以根据需要创建用于在 C++ 或 C 中索引对象的方法。 有很多方法可以解决这个问题。

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

你说你还没有实现 Python 异常抛出 - 这就是问题所在。 来自 PEP 234:

定义了一个新的异常 StopIteration,它可用于表示迭代结束。

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

  1. 解释器循环遍历列表的自定义iternext函数
  2. 您的函数到达数组的末尾,而不是正确设置StopIteration异常,只是返回您的“幻数”。
  3. 解释器看不到停止迭代的充分理由,只是继续打印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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM