tags:

views:

1397

answers:

7

Is there a method like isiterable? The only solution I have found so far is to call

getattr(myObj, '__iter__', False)

But I am not sure how fool-proof this is.

+17  A: 

I Checking for __iter__ works on sequence types, but it would fail on e.g. strings. I would like to know the right answer too, until then, here is one possibility (which would work on strings, too):

try:
    some_object_iterator = iter(some_object)
except TypeError, te:
    print some_object, 'is not iterable'

The iter built-in:

>>> help(iter)
 1 Help on built-in function iter in module __builtin__:
 2 
 3 iter(...)
 4     iter(collection) -> iterator
 5     iter(callable, sentinel) -> iterator
 6     
 7     Get an iterator from an object.  In the first form, the argument must
 8     supply its own iterator, or be a sequence.
 9     In the second form, the callable is called until it returns the sentinel.

II Another general pythonic approach is to assume an iterable, then fail gracefully if it does not work on the given object. The python glossary:

Pythonic programming style that determines an object's type by inspection of its method or attribute signature rather than by explicit relationship to some type object ("If it looks like a duck and quacks like a duck, it must be a duck.") By emphasizing interfaces rather than specific types, well-designed code improves its flexibility by allowing polymorphic substitution. Duck-typing avoids tests using type() or isinstance(). Instead, it typically employs the EAFP (Easier to Ask Forgiveness than Permission) style of programming.

...

try:
    [ e for e in my_object]
except TypeError:
    print my_object, 'is not iterable'

III The collections module provides some abstract base classes, which allow to ask classes or instances if they provide particular functionality, for example:

import collections

if isinstance(e, collections.Iterable):
    # e is iterable
The MYYN
`[e for e in my_object]` can raise an exception for other reasons, ie `my_object` is undefined or possible bugs in `my_object` implementation.
Nick D
added explicit ``TypeError``...
The MYYN
A string *is* a sequence (`isinstance('', Sequence) == True`) *and* as any sequence it *is* iterable (`isinstance('', Iterable)`). Though `hasattr('', '__iter__') == False` and it might be confusing.
J.F. Sebastian
If `my_object` is very large (say, infinite like `itertools.count()`) your list comprehension will take up a lot of time/memory. Better to make a generator, which will never try to build a (potentially infinite) list.
Chris Lutz
+1 for the last answer
Michael Mior
+8  A: 

This isn't sufficient: the object returned by __iter__ must implement the iteration protocol (i.e. next method). See the relevant section in the documentation.

In Python, a good practice is to " try and see " instead of "checking".

jldupont
"duck typing" I believe? :)
willem
@willem: or "don't ask for permission but for forgiveness" ;-)
jldupont
+5  A: 

You could try this:

def iterable(a):
    try:
        (x for x in a)
        return True
    except TypeError:
        return False

If we can make a generator that iterates over it (but never use the generator so it doesn't take up space), it's iterable. Seems like a "duh" kind of thing. Why do you need to determine if a variable is iterable in the first place?

Chris Lutz
What about `iterable(itertools.repeat(0))`? :)
badp
@badp, the `(x for x in a)` just creates a generator, it doesn't do any iteration on a.
catchmeifyoutry
Oh, nice! I didn't know about that one. Sorry.
badp
+2  A: 
try:
  #treat object as iterable
except TypeError, e:
  #object is not actually iterable

Don't run checks to see if your duck really is a duck to see if it is iterable or not, treat it as if it was and complain if it wasn't.

badp
Technically, during iteration your computation might throw a `TypeError` and throw you off here, but basically yes.
Chris Lutz
I know in .NET it was a bad idea to have exceptions handle program flow, as exceptions were **slow**. How quickly does python handle exceptions?
willem
@willem: Please use timeit to perform a benchmark. Python exceptions are often faster than if-statements. They can take a slightly shorter path through the interpreter.
S.Lott
@willem: IronPython has slow (compared to CPython) exceptions.
J.F. Sebastian
+20  A: 

Duck typing

try:
    iterator = iter(theElement)
except TypeError:
    # not iterable
else:
    # iterable

# for obj in iterator:
#     pass

Type checking

Use the Abstract Base Classes. They need at least Python 2.6 and work only for new-style classes.

import collections

if isinstance(theElement, collections.Iterable):
    # iterable
else:
    # not iterable
Georg
+1 for being the first to mention `collections.Iterable`!
Scott Griffiths
Awesome and simple. Gogogo!!
jathanism
This should have been the one accepted. Another frustrating result from SO readers.
Brandon Corfman
`isinstance(x, ABC)` doesn't work on instances of old-style classes.
J.F. Sebastian
@J.F. Sebastian: Thanks, I didn't know that.
Georg
ABC classes? Someone has RAS syndrome. http://en.wikipedia.org/wiki/RAS_syndrome
Chris Lutz
A: 

Found a nice solution here:

isiterable = lambda obj: isinstance(obj, basestring) \
    or getattr(obj, '__iter__', False)
jbochi
+2  A: 

On python <= 2.5, you can't and shouldn't - iterable was an "informal" interface.

But since python2.6 and 3.0 you can leverage the new ABC (abstract base class) infrastructure along with some builtin ABCs which are available in the collections module:

from collections import Iterable

class MyObject(object):
    pass

mo = MyObject()
print isinstance(mo, Iterable)
Iterable.register(MyObject)
print isinstance(mo, Iterable)

print isinstance("abc", Iterable)

Now, whether this is desiderable or actually works, is just a matter of conventions. As you can see, you can register a non-iterable object as Iterable - and it will raise an exception at runtime. Hence, isinstance acquires a "new" meaning - it just checks for "declared" type compatibility, which is a good way to go in Python.

On the other hand, if your object does not satifsy the interface you need, what are you going to do? take the following example:

from collections import Iterable
from traceback import print_exc

def check_and_raise(x):
    if not isinstance(x, Iterable):
        raise TypeError, "%s is not iterable" % x
    else:
        for i in x:
            print i

def just_iter(x):
    for i in x:
        print i


class NotIterable(object):
    pass

if __name__ == "__main__":
    try:
        check_and_raise(5)
    except:
        print_exc()
        print

    try:
        just_iter(5)
    except:
        print_exc()
        print



    try:
        Iterable.register(NotIterable)
        ni = NotIterable()
        check_and_raise(ni)
    except:
        print_exc()
        print

If the object doesn't satifsy what you expect, you just throw a TypeError, but if the proper ABC has been registered, your check is unuseful. On the contrary, if the __iter__ method is available python will automatically recognize object of that class as being Iterable.

So, if you just expect an iterable, iterate over it and forget it. On the other hand, if you need to do different things depending on input type, you might find the ABC infrastracture pretty useful.

Alan Franzoni
+1: ABC's rule.
S.Lott
don't use bare `except:` in the example code for beginners. It promotes bad practice.
J.F. Sebastian
J.F.S: I wouldn't, but I needed to go through multiple exception-raising code and I didn't want to catch the specific exception... I think the purpose of this code is pretty clear.
Alan Franzoni