在纯Python中,通常找出一个对象是否可迭代的解决方案是调用iter(...)
并查看发生了什么(例如,“流畅的Python”广为流传):
def is_iterable(obj):
try:
iter(obj)
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;
}
else if (PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_Clear();
return 0;
}
else{
return -1;
}
}
但这可能不是你所说的“可迭代”,那么可以调整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;
C()
和D()
会被归类为可迭代对象(而第一个is_iterator
版本则不是)。此外,如果is_iterator2
返回1,并不意味着PyObject_GetIter
不会返回NULL
,正如上述类所示。
PyIter_Check()
函数用于检查对象上是否可以调用next
。所以,在这里是不起作用的。在我的例子中,当对iter
而不是arg
进行调用时,PyIter_Check()
将返回正确的结果。事实上,我已经尝试过这个,并且无论arg
是数字还是列表,它总是返回false。我需要测试arg
是列表、元组还是其他容器。元组没有next
方法。 - undefinedPyErr_Clear()
来消除异常。@falsetru - 这是否也检查它是否可迭代,还是仅检查它是否已经是一个迭代器? - undefinedPython/import.c
中有一个代码调用PyObject_GetIter
和PyErr_Clear
。现在已经被PyErr_WriteUnraisable()
替代。我认为在你的情况下调用PyErr_Clear
是安全的。 - undefined