【问题标题】:Extending python with C: Pass a list to PyArg_ParseTuple使用 C 扩展 python:将列表传递给 PyArg_ParseTuple
【发布时间】:2014-04-22 21:22:08
【问题描述】:

我一直在尝试用 C 扩展 python,到目前为止,基于 documentation,我在编写小型 C 函数并用 Python 扩展它方面取得了相当大的成功。

但是,我现在遇到了一个相当简单的问题 - 我无法找到解决方案。所以,我想做的是将double list 传递给我的C 函数。例如,要传递int,我执行以下操作:

int squared(int n)
{
    if (n > 0)
        return n*n;
    else
        return 0;
}

static PyObject*
squaredfunc(PyObject* self, PyObject* args)
{
    int n;

    if (!PyArg_ParseTuple(args, "i", &n))
        return NULL;

    return Py_BuildValue("i", squared(n));
}

这会将int n 毫无问题地传递给名为squared 的C 函数。

但是,如何将list 传递给C 函数?我确实尝试用谷歌搜索它并阅读文档,但到目前为止,我还没有找到任何有用的东西。

如果有人能指出我正确的方向,我将不胜感激。

谢谢。

【问题讨论】:

  • 我认为您需要"O" 而不是"i" 来表示您接受任意对象。
  • @mgilson:谢谢。但是,我该如何更改我的 int n 声明以反映这一点?我应该只做double n[]吗?
  • 不,应该是PyObject *lst;,或者我想的类似的东西。
  • @mgilson:我想知道是否有一个例子可以让我查看并尝试理解?再次感谢。
  • 不确定。我知道的不多,我所知道的是阅读您已经链接的python源代码/文档。

标签: python c python-c-api python-c-extension


【解决方案1】:

PyArg_ParseTuple 函数允许您使用格式字符串 "O!" 直接转换为 Python 对象子类型(注意,这与普通的 "O" 不同)。如果参数与指定的 PyObject 类型不匹配,则会抛出 TypeError。例如:

PyObject *pList;
PyObject *pItem;
Py_ssize_t n;
int i;

if (!PyArg_ParseTuple(args, "O!", &PyList_Type, &pList)) {
    PyErr_SetString(PyExc_TypeError, "parameter must be a list.");
    return NULL;
}

n = PyList_Size(pList);
for (i=0; i<n; i++) {
    pItem = PyList_GetItem(pList, i);
    if(!PyInt_Check(pItem)) {
        PyErr_SetString(PyExc_TypeError, "list items must be integers.");
        return NULL;
    }
}

附带说明,请记住使用PyList_GetItem 遍历列表会返回对每个项目的借用 引用,因此您不需要Py_DECREF(item) 来处理reference count。另一方面,使用有用的Iterator Protocol(参见@NathanBinkert 的answer),返回的每个项目都是一个 引用——所以你必须记住在使用Py_DECREF(item) 完成时丢弃它.

【讨论】:

  • 有没有办法禁止 PyArg_ParseTuple 抛出异常?我想要一个可以接受多种类型的函数。
  • @Tony:PyArg_ParseTuple(args, "O!", &amp;PyList_Type, &amp;pList)"O!", &amp;PyList_Type格式字符串的重点是让它检查类型并抛出TypeError。如果您不希望它检查类型,则只需使用 PyArg_ParseTuple(args, "O", &amp;pList) 代替。 (顺便说一句,&amp;PyList_Type 中的 &amp; 非常重要!第一次被我咬了。)
  • @Tony:或者,如果“可以采用多种类型的函数”是指“某些参数是可选的函数”,则由 the "|" format specifier. 处理如果您仍有疑问这个领域,我建议你问自己单独的 SO 问题。
【解决方案2】:

PyArg_ParseTuple 只能处理简单的 C 类型、复数、char *PyStringObject *PyUnicodeObject *PyObject *。使用 PyListObject 的唯一方法是使用“O”的一些变体并将对象提取为 PyObject *。然后您可以使用List Object API 来检查该对象确实是一个列表(PyList_Check)。然后您可以使用PyList_SizePyList_GetItem 来遍历列表。请注意,在迭代时,您将获得PyObject *,并且必须使用floating point API 来访问实际值(通过执行PyFloat_CheckPyFloat_AsDouble。)作为List API 的替代方案,您可以使用更多灵活并使用iterator protocol(在这种情况下你应该只使用PyIter_Check)。这将允许您迭代任何支持迭代器协议的东西,例如列表、元组、集合等。

最后,如果你真的希望你的函数接受double n[] 并且你想避免所有手动转换,那么你应该使用boost::python 之类的东西。学习曲线和 API 更复杂,但 boost::python 会自动为您处理所有转换。

这是一个使用迭代器协议进行循环的示例(未经测试,您需要填写错误处理代码):

PyObject *obj;

if (!PyArg_ParseTuple(args, "O", &obj)) {
  // error
}

PyObject *iter = PyObject_GetIter(obj);
if (!iter) {
  // error not iterator
}

while (true) {
  PyObject *next = PyIter_Next(iter);
  if (!next) {
    // nothing left in the iterator
    break;
  }

  if (!PyFloat_Check(next)) {
    // error, we were expecting a floating point value
  }

  double foo = PyFloat_AsDouble(next);
  // do something with foo
}

【讨论】:

    猜你喜欢
    • 2012-05-25
    • 2018-04-26
    • 2017-12-27
    • 2019-08-30
    • 2021-11-03
    • 2016-10-20
    • 2018-06-27
    • 1970-01-01
    相关资源
    最近更新 更多