在Python C扩展中检查对象是否可迭代

3

我需要编写一个函数,根据Python参数是否可迭代来执行不同的操作。以下是代码示例:

PyObject *iter = PyObject_GetIter(arg); // arg is a PyObject*
if (iter) {
  // do iterable things
} else {
  // do non-iterable things
}

然而,看起来如果arg不可迭代,则不仅iter == NULL,还会抛出异常。如何正确处理这种情况?我只需调用PyErr_Clear()并希望没有设置其他错误吗?


https://docs.python.org/3/c-api/iter.html#c.PyIter_Check - undefined
据我所知,PyIter_Check()函数用于检查对象上是否可以调用next。所以,在这里是不起作用的。在我的例子中,当对iter而不是arg进行调用时,PyIter_Check()将返回正确的结果。事实上,我已经尝试过这个,并且无论arg是数字还是列表,它总是返回false。我需要测试arg是列表、元组还是其他容器。元组没有next方法。 - undefined
可能只需要调用PyErr_Clear()来消除异常。@falsetru - 这是否也检查它是否可迭代,还是仅检查它是否已经是一个迭代器? - undefined
抱歉,我误读了问题。在Python/import.c中有一个代码调用PyObject_GetIterPyErr_Clear。现在已经被PyErr_WriteUnraisable()替代。我认为在你的情况下调用PyErr_Clear是安全的。 - undefined
1个回答

3

在纯Python中,通常找出一个对象是否可迭代的解决方案是调用iter(...)并查看发生了什么(例如,“流畅的Python”广为流传):

def is_iterable(obj):
    try:
        iter(obj)  # ok, it worked
        return True
    except TypeError:
        return False

更多细节,请参见这个很棒的答案

这基本上也是@falsetru在评论中提出的建议——尝试并清除错误,如果PyObject_GetIter失败:

int is_iterator(PyObject *obj){
    PyObject *it =  PyObject_GetIter(obj);
    if(it != NULL){
        Py_DECREF(it);
        return 1; // object can be iterated
    }
    else if (PyErr_ExceptionMatches(PyExc_TypeError)) {
        PyErr_Clear();
        return 0; // is not an iterator
    }
    else{
        return -1; // error
    }
}

但这可能不是你所说的“可迭代”,那么可以调整PyObject_GetIter的实现以满足你的需求,例如:

int is_iterator2(PyObject *obj) {
    return Py_TYPE(obj)->tp_iter != NULL || PySequence_Check(obj);
}

像通常的算法一样,is_iterator2查找tp_iter插槽(即__iter__函数)是否存在,如果不存在,则通过序列协议__getitem__进行回退。然而,与第一个版本不同的是,tp_iter插槽没有被调用,其结果也没有被检查是否为迭代器。

class C:
    def __iter__(self):
        raise BufferError()
        
class D:
    def __iter__(self):
        return 1; # isn't iterator

C()D()会被归类为可迭代对象(而第一个is_iterator版本则不是)。此外,如果is_iterator2返回1,并不意味着PyObject_GetIter不会返回NULL,正如上述类所示。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接