SWIG类型映射:Python输入和输出数组

4 投票
3 回答
5445 浏览
提问于 2025-04-16 03:49

我有一个C语言写的函数,想在Python中使用:

extern int convertAtoB( stateStruct *myStruct,
                        const double PointA[3],
                        double PointB[3]);

我想用SWIG这个工具,感觉需要定义一个类型映射(typemap),来把两个点(输入的PointA和输出的PointB)转换成Python能用的格式。不过在typemaps.i文件里似乎没有适合这个的类型映射,所以我得自己定义一个。我在SWIG的文档里找不到关于数组的例子。

我想像这样使用这个库:

s = externalStruct()
point_a = [1, 2, 3]
result, point_b = convertAtoB(s, point_a)
print point_b
"expect [4, 5, 6]"

我该怎么做呢?谢谢!

3 个回答

1

这是一个老话题,但我来回答一下,因为关于SWIG的帖子并不多。

为了特别针对上面的情况

%typemap(in, numinputs=0) double PointB[3] {
  double tmp[3];
  $1 = tmp;
}

%typemap(argout) double PointB[3] {
  PyObject *o = PyList_New(3);
  int i;
  for(i=0; i<3; i++)
  {
    PyList_SetItem(o, i, PyFloat_FromDouble($1[i]));
  }
  $result = o;
}
3

你快到了。为了去掉Python函数签名中的多余参数,你需要把 %typemap(in)PointB[3] 的设置改成 %typemap(in,numinputs=0),这样可以告诉SWIG忽略这个输入值(反正你已经在复制它了)。这样就能把多余的参数从Python的方法签名中去掉。

不过我不太确定你是否需要为这个特殊情况复制整个 %typemap(in)。可能有办法重用现有的typemap,但我不知道怎么做。否则你可能会得到一个额外的

%typemap(in,numinputs=0) double PointB[3] (double temp[$1_dim0]) {
  int i;
  if (!PySequence_Check($input)) {
    PyErr_SetString(PyExc_ValueError,"Expected a sequence");
    return NULL;
  }
  if (PySequence_Length($input) != $1_dim0) {
    PyErr_SetString(PyExc_ValueError,"Size mismatch. Expected $1_dim0 elements");
    return NULL;
  }
  for (i = 0; i < $1_dim0; i++) {
    PyObject *o = PySequence_GetItem($input,i);
    if (PyNumber_Check(o)) {
      temp[i] = (double) PyFloat_AsDouble(o);
    } else {
      PyErr_SetString(PyExc_ValueError,"Sequence elements must be numbers");      
      return NULL;
    }
  }
  $1 = temp;
}
2

这是我找到的一个解决方案,但可能不是最好的:

%typemap(in) double[ANY] (double temp[$1_dim0]) {
  int i;
  if (!PySequence_Check($input)) {
    PyErr_SetString(PyExc_ValueError,"Expected a sequence");
    return NULL;
  }
  if (PySequence_Length($input) != $1_dim0) {
    PyErr_SetString(PyExc_ValueError,"Size mismatch. Expected $1_dim0 elements");
    return NULL;
  }
  for (i = 0; i < $1_dim0; i++) {
    PyObject *o = PySequence_GetItem($input,i);
    if (PyNumber_Check(o)) {
      temp[i] = (double) PyFloat_AsDouble(o);
    } else {
      PyErr_SetString(PyExc_ValueError,"Sequence elements must be numbers");      
      return NULL;
    }
  }
  $1 = temp;
}

这是我最终找到的文档中的一个例子,它展示了如何把Python的列表转换成数组。接下来的部分比较难,我需要把几个例子拼凑在一起,才能把返回的数组转换成Python的列表:

%typemap(argout) double PointB[3]{
    PyObject *o = PyList_New(3);
    int i;
    for(i=0; i<3; i++)
    {
        PyList_SetItem(o, i, PyFloat_FromDouble($1[i]));
    }
    $result = o;
}

不过,我必须为API中的每个返回值都创建一个这样的东西。而且我还得用一个虚拟值作为参数来调用它:

point_b = convertAtoB(s, point_a, dummy)

有没有更好的方法呢?

撰写回答