In Python, how do I determine if an object is iterable?

前端 未结 21 2230
太阳男子
太阳男子 2020-11-22 00:35

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

hasattr(myObj, \'__iter__\')

But I am not

相关标签:
21条回答
  • 2020-11-22 01:00
    def is_iterable(x):
        try:
            0 in x
        except TypeError:
            return False
        else:
            return True
    

    This will say yes to all manner of iterable objects, but it will say no to strings in Python 2. (That's what I want for example when a recursive function could take a string or a container of strings. In that situation, asking forgiveness may lead to obfuscode, and it's better to ask permission first.)

    import numpy
    
    class Yes:
        def __iter__(self):
            yield 1;
            yield 2;
            yield 3;
    
    class No:
        pass
    
    class Nope:
        def __iter__(self):
            return 'nonsense'
    
    assert is_iterable(Yes())
    assert is_iterable(range(3))
    assert is_iterable((1,2,3))   # tuple
    assert is_iterable([1,2,3])   # list
    assert is_iterable({1,2,3})   # set
    assert is_iterable({1:'one', 2:'two', 3:'three'})   # dictionary
    assert is_iterable(numpy.array([1,2,3]))
    assert is_iterable(bytearray("not really a string", 'utf-8'))
    
    assert not is_iterable(No())
    assert not is_iterable(Nope())
    assert not is_iterable("string")
    assert not is_iterable(42)
    assert not is_iterable(True)
    assert not is_iterable(None)
    

    Many other strategies here will say yes to strings. Use them if that's what you want.

    import collections
    import numpy
    
    assert isinstance("string", collections.Iterable)
    assert isinstance("string", collections.Sequence)
    assert numpy.iterable("string")
    assert iter("string")
    assert hasattr("string", '__getitem__')
    

    Note: is_iterable() will say yes to strings of type bytes and bytearray.

    • bytes objects in Python 3 are iterable True == is_iterable(b"string") == is_iterable("string".encode('utf-8')) There is no such type in Python 2.
    • bytearray objects in Python 2 and 3 are iterable True == is_iterable(bytearray(b"abc"))

    The O.P. hasattr(x, '__iter__') approach will say yes to strings in Python 3 and no in Python 2 (no matter whether '' or b'' or u''). Thanks to @LuisMasuelli for noticing it will also let you down on a buggy __iter__.

    0 讨论(0)
  • 2020-11-22 01:01

    I've been studying this problem quite a bit lately. Based on that my conclusion is that nowadays this is the best approach:

    from collections.abc import Iterable   # drop `.abc` with Python 2.7 or lower
    
    def iterable(obj):
        return isinstance(obj, Iterable)
    

    The above has been recommended already earlier, but the general consensus has been that using iter() would be better:

    def iterable(obj):
        try:
            iter(obj)
        except Exception:
            return False
        else:
            return True
    

    We've used iter() in our code as well for this purpose, but I've lately started to get more and more annoyed by objects which only have __getitem__ being considered iterable. There are valid reasons to have __getitem__ in a non-iterable object and with them the above code doesn't work well. As a real life example we can use Faker. The above code reports it being iterable but actually trying to iterate it causes an AttributeError (tested with Faker 4.0.2):

    >>> from faker import Faker
    >>> fake = Faker()
    >>> iter(fake)    # No exception, must be iterable
    <iterator object at 0x7f1c71db58d0>
    >>> list(fake)    # Ooops
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/home/.../site-packages/faker/proxy.py", line 59, in __getitem__
        return self._factory_map[locale.replace('-', '_')]
    AttributeError: 'int' object has no attribute 'replace'
    

    If we'd use insinstance(), we wouldn't accidentally consider Faker instances (or any other objects having only __getitem__) to be iterable:

    >>> from collections.abc import Iterable
    >>> from faker import Faker
    >>> isinstance(Faker(), Iterable)
    False
    

    Earlier answers commented that using iter() is safer as the old way to implement iteration in Python was based on __getitem__ and the isinstance() approach wouldn't detect that. This may have been true with old Python versions, but based on my pretty exhaustive testing isinstance() works great nowadays. The only case where isinstance() didn't work but iter() did was with UserDict when using Python 2. If that's relevant, it's possible to use isinstance(item, (Iterable, UserDict)) to get that covered.

    0 讨论(0)
  • 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?

    0 讨论(0)
  • 2020-11-22 01:03

    I found a nice solution here:

    isiterable = lambda obj: isinstance(obj, basestring) \
        or getattr(obj, '__iter__', False)
    
    0 讨论(0)
  • 2020-11-22 01:03

    Kinda late to the party but I asked myself this question and saw this then thought of an answer. I don't know if someone already posted this. But essentially, I've noticed that all iterable types have __getitem__() in their dict. This is how you would check if an object was an iterable without even trying. (Pun intended)

    def is_attr(arg):
        return '__getitem__' in dir(arg)
    
    0 讨论(0)
  • 2020-11-22 01:04

    According to the Python 2 Glossary, iterables are

    all sequence types (such as list, str, and tuple) and some non-sequence types like dict and file and objects of any classes you define with an __iter__() or __getitem__() method. Iterables can be used in a for loop and in many other places where a sequence is needed (zip(), map(), ...). When an iterable object is passed as an argument to the built-in function iter(), it returns an iterator for the object.

    Of course, given the general coding style for Python based on the fact that it's “Easier to ask for forgiveness than permission.”, the general expectation is to use

    try:
        for i in object_in_question:
            do_something
    except TypeError:
        do_something_for_non_iterable
    

    But if you need to check it explicitly, you can test for an iterable by hasattr(object_in_question, "__iter__") or hasattr(object_in_question, "__getitem__"). You need to check for both, because strs don't have an __iter__ method (at least not in Python 2, in Python 3 they do) and because generator objects don't have a __getitem__ method.

    0 讨论(0)
提交回复
热议问题