15

I've seen a couple posts recommending isinstance(obj, collections.Sequence) instead of hasattr(obj, '__iter__') to determine if something is a list.

len(object) or hasattr(object, __iter__)?

Python: check if an object is a sequence

At first I was excited because testing if an object has __iter__ always seemed dirty to me. But after further review this still seems to be the best solution because none of the isinstance tests on collection yield the same results. collections.Sequence is close but it returns True for strings.

hasattr(obj, '__iter__')
    set([]): True
    {}: True
    []: True
    'str': False
    1: False

isinstance(obj, collections.Iterable)
    set([]): True
    {}: True
    []: True
    'str': True
    1: False

isinstance(obj, collections.Iterator)
    set([]): False
    {}: False
    []: False
    'str': False
    1: False

isinstance(obj, collections.Sequence)
    set([]): False
    {}: False
    []: True
    'str': True
    1: False

Here is the code I used to generate this:

import collections

testObjs = [
    set(),
    dict(),
    list(),
    'str',
    1
]

print "hasattr(obj, '__iter__')"
for obj in testObjs:
    print '    %r: %r' % (obj, hasattr(obj, '__iter__'))
print

print "isinstance(obj, collections.Iterable)"
for obj in testObjs:
    print '    %r: %r' % (obj, isinstance(obj, collections.Iterable))
print

print "isinstance(obj, collections.Iterator)"
for obj in testObjs:
    print '    %r: %r' % (obj, isinstance(obj, collections.Iterator))
print

print "isinstance(obj, collections.Sequence)"
for obj in testObjs:
    print '    %r: %r' % (obj, isinstance(obj, collections.Sequence))
print

Am I missing something or is hasattr(obj, '__iter__') still the best option for testing if something is iterable?

EDIT: I am only interested in detecting the builtin types: dict, list, and set.(EDIT: this is foolish :))

EDIT: I should have included the use case that got me looking into this. I have a function that takes an arg that can be a single value or a sequence. So I want to detect what it is and turn it into a sequence if it's a single value so I can deal with it as a sequence after that.

if hasattr(arg, '__iter__'):
    arg= set(arg)
else:
    arg= set([arg])

One solution to this is just to let it throw an exception if the object cannot be iterated. But that doesn't work in my use case. Another solution is to use something like:

import collections

def issequenceforme(obj):
    if isinstance(obj, basestring):
        return False
    return isinstance(obj, collections.Sequence)

From: Python: check if an object is a sequence

But this requires this function to be defined which makes me not want to use it.

It looks like hasattr(arg, '__iter__') is still the best option.

Community
  • 1
  • 1
keegan3d
  • 10,357
  • 9
  • 53
  • 77
  • 6
    http://stackoverflow.com/questions/1952464/in-python-how-do-i-determine-if-a-variable-is-iterable – Josh Lee Aug 25 '11 at 22:01
  • 3
    Personally, I check `isinstance( obj, basestring )` (iterable but not usually something you want to iterate over) and if not, iterate on it, catching the exception. – Russell Borogove Aug 25 '11 at 22:55

5 Answers5

6

The collections.Iterable will guarantee the object is iterable or not (like using for x in obj) but checking __iter__ will not.

A string is a iterable datatype but on Python2.x it doesn't have a __iter__ method.

JBernardo
  • 32,262
  • 10
  • 90
  • 115
2

Question: I've seen a couple posts recommending isinstance(obj, collections.Sequence) instead of hasattr(obj, '__iter__') to determine if something is a list.

To determine whether something is iterable, the most reliable method is to attempt to create an iterator. That will determine whether an object is actually iterable:

def is_iterable(obj):
    try:
        iter(obj)
    except TypeError:
        return False
    else:
        return True

A slightly less reliable, but clean looking approach is to test against the abstract base class. That will test whether an object has the __iter__() method or whether it is registered as an Iterable (for example, an instance of str doesn't have an __iter__() method but it is registered as an iterable):

isinstance(obj, collections.Iterable)

If you're specifically interested in lists and list-like objects, you could test against the MutableSequence ABC:

isinstance(obj, collections.MutableSequence)

That said, if you're relying on an object having a particular list-like behavior, the Pythonic way is to assume the method is present and catch the exception if it is not. This is called duck typing.

Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
2

You can also add __iter__ to your own classes, so any test short of hasattr(obj, '__iter__') would not be guaranteed to detect those. You didn't specify in the question that you only care about builtin collections.

Ben Jackson
  • 90,079
  • 9
  • 98
  • 150
  • I am only interested in detecting the builtin types like `dict`, `list`, and `set`. I will add this to my question. – keegan3d Aug 25 '11 at 22:04
2

collections.Sequence is close but it returns True for strings.

  • collections.Iterable is for an iterable object, that is, some form of container which allows you to iterate over the objects it contains, one by one. Strings are included in this since you can iterate over each character is a string.
  • collections.Sequence is for a sub-set of iterable objects which guarantee the order of iteration. Lists and strings will return True as the order of iteration is always the same for the same list or string, while sets and dictionaries will return False since you don't know what order the internal objects will come out in.

If you are sure you only want to detect the built-in types (often considered a suspect practice, with duck typing being preferred), then you can do so:

if isinstance(arg, (set, list, dict, tuple)):
    print "I got a built-in iterable."
else:
    print "Hey, this ain't iterable!"

However, the solution you came up with is better - handle any special cases (such as strings, in your case) and then use duck typing. This then lets others use whatever container fits their needs, and puts the responsibility of making sure it fits with what your code requires on them.

Blair
  • 15,356
  • 7
  • 46
  • 56
1

After talking to some colleagues I concluded that hasattr(arg, '__iter__') may be a nice one liner but it is far from perfect, as you guys have also pointed out. This is what I ended up with:

if isinstance(arg, basestring):
    arg= set([arg])
else:
    try:
        selection= set(arg)
    except TypeError:
        selection= set([arg])
keegan3d
  • 10,357
  • 9
  • 53
  • 77
  • The check for `basestring` is common and useful. Another useful check is for `collections.Mapping` (rather than `dict`), especially since you seem to be interested in sets, and the keys of a `Mapping` are always a set. – hobs Mar 04 '14 at 19:22