当前位置: 首页 > 面试题库 >

SWIG将C库与Python接口(从C“序列”结构创建“可迭代” Python数据类型)

滕渝
2023-03-14
问题内容

我已经为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中的元素:枚举能力

根据PEP234, 一个对象可以与遍历“为”是否实现_
ITER ()或 的GetItem _()

。然后,使用该逻辑,我认为通过将以下属性(通过重命名)添加到我的SWIG接口文件中,我将具有所需的行为(除了上面的要求#1-我仍然不知道如何实现):

__len__
__getitem__
__setitem__

我现在能够在python中索引C对象。我尚未实现Python异常抛出,但是如果超出数组范围,则返回一个幻数(错误代码)。

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

for i in py_cstruct:
    print i

Python进入一个无限循环,该循环只是在控制台上简单地打印出上面提到的幻数(错误)。这向我表明索引存在问题。

最后但并非最不重要的一点,我该如何实施要求1?这涉及(据我了解):

  • 从python处理’函数调用 list()
  • 从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

然后完成。

也可以使用一种类型映射来直接映射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使用任何返回a的函数的返回值创建一个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);
}

对于链表之类的容器,使用迭代器更有意义,但是出于完整性考虑,这是您可以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;
  }
}

对容器进行迭代的要求是,容器需要实现__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()类似于第一种类型。我在原始答案中没有这样做,因为我认为这要比具有两种不同的类型(容器和该容器的迭代器)不清楚



 类似资料:
  • 问题内容: 我正在尝试使用swig从python使用以下原型访问C函数: Swig创建.so没问题,我可以将其导入python,但是当我尝试使用以下命令进行访问时: 我得到以下回溯: 该指针应该是一个int数组,其大小由memoryCells定义 问题答案: 如果可以,请使用ctypes。更简单。但是,由于您要求输入SWIG,因此需要的是一个描述如何处理int *的类型图。SWIG不知道可以指向多

  • 到目前为止,我已经编写了两个rails应用程序,这无疑让我对ruby rails非常满意。但我不能对C说同样的话。我甚至不知道我在看什么,说实话,每次我看C的时候,它看起来像通心粉。 我正在尝试构建一个spotify web应用程序。web API太差劲了,所以我不得不用这个:https://developer.spotify.com/technologies/libspotify/ 文档:htt

  • 问题内容: 我正在尝试使用SWIG为python包装一个C ++库。该库通过将 某些类型的回调函数 传递给类方法来频繁使用回调函数。 现在,在包装代码之后,我想从python创建回调逻辑。这可能吗?这是我正在尝试发现的实验..目前不起作用。 头文件和swig文件如下: paska.h: paska.i: 最后,我在python中测试.. 最后一行抛出“ TypeError:方法’handleri_

  • 问题内容: 我在header.h内部定义了一个结构,如下所示: 当我用这种结构初始化一个对象时,我可以访问整数/双精度数,但不能访问数组。 如何访问读/写中的值? 问题答案: 最简单的方法是将数组包装在内,然后可以提供额外的方法来满足“可订阅”的要求。 我整理了一个小例子。假定您使用的是C ++,但是从中构造等效的C版本相当简单,它只需要重复一些即可。 首先,具有要包装的C ++头文件和用于包装固

  • 我正在阅读这个关于如何绑定Python和C的教程。我正在使用Python v3.7.1,所以我不得不使用新的Python接口(与示例不同),所以现在我有: 加法器。c 似乎一切都是正确的,我可以通过调用addList来运行Python代码。add()函数。但在构建模块时,我得到了以下输出(请注意,在出现此错误后,我可以很好地运行Python代码): $pythonsetup.py安装运行安装运行构

  • 问题内容: 我继承了一个包含许多大类的项目,这些大类仅由类对象(整数,字符串等)组成。我希望能够检查是否存在属性,而无需手动定义属性列表。 是否可以使用标准语法使其本身可迭代的python 类 ?也就是说,我希望能够使用(甚至使用)遍历类的所有属性,而无需首先创建类的实例。我想我可以通过定义来做到这一点,但是到目前为止,我还没有完全找到想要的东西。 通过添加如下方法,我已经实现了一些我想要的东西: