In pure Python, the usual solution for finding out whether an object is iterable is to call iter(...)
and to see what happens (it was for example popularized by "Fluent Python"):
def is_iterable(obj):
try:
iter(obj) # ok, it worked
return True
except TypeError:
return False
for more details, see this great answer.
This is also basically the @falsetru's proposal in comments - to try and to clear error, if PyObject_GetIter
fails:
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
}
}
But it might not be what you mean by "being iterable", then one can adjust the implementation of PyObject_GetIter
to your needs, for example:
int is_iterator2(PyObject *obj) {
return Py_TYPE(obj)->tp_iter != NULL || PySequence_Check(obj);
}
As in usual algorithm, is_iterator2
looks up if the tp_iter
-slot (i.e. __iter__
-function) is present and if not falls back to the sequence protocol via __getitem__
. However, differently as in the first version, the tp_iter
slot isn't called and its result isn't checked for being an iterator, i.e. for
class C:
def __iter__(self):
raise BufferError()
class D:
def __iter__(self):
return 1; # isn't iterator
C()
and D()
would be classified as iterable (which would not be the case for the first is_iterator
version). Also if is_iterator2
returned 1, it doesn't mean PyObject_GetIter
doesn't return NULL
as can be seen for the above classes.