Is there a method like isiterable
? The only solution I have found so far is to call
hasattr(myObj, '__iter__')
But I am not sure how fool-proof this is.
Is there a method like isiterable
? The only solution I have found so far is to call
hasattr(myObj, '__iter__')
But I am not sure how fool-proof this is.
Checking for __iter__
works on sequence types, but it would fail on e.g. strings in Python 2. 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 as te:
print(some_object, 'is not iterable')
The iter
built-in checks for the __iter__
method or in the case of strings the __getitem__
method.
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')
The collections
module provides some abstract base classes, which allow to ask classes or instances if they provide particular functionality, for example:
from collections.abc import Iterable
if isinstance(e, Iterable):
# e is iterable
However, this does not check for classes that are iterable through __getitem__
.
[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. –
Bibliotaph isinstance('', Sequence) == True
) and as any sequence it is iterable (isinstance('', Iterable)
). Though hasattr('', '__iter__') == False
and it might be confusing. –
Compellation 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. –
Hydromedusa isinstance(e, Iterable)
? And why do you say it's "another case"? Do you mean old-style classes? –
Alius TypeError
doesn't solve the problem described by Nick thus Shaung's comment in a sense just reiterates Nick's comment. Checking description of an exception as Arne's suggested is always a bad idea as this description is implementation detail. –
Isopleth TypeError
may be raised from deeper layers, and an exception message may change in the next version. I guess that's why I only feel comfortable with EAFP approach when the behavior I desire is the same regardless of what caused the exception. –
Alius hasattr(u"hello", '__iter__')
returns True
–
Installation class X(object): __iter__ = 1
. 3 actually doesn't work with this. –
Howling __iter__
are automatically subclasses of collections.Iterable
Could you please provide reference for this statement? –
Isopleth len()
right? so wouldn't a simple try: l = len(myObj)
be enough to determine if myObj
is an iterable? –
Commandeer xrange
is one object which has no __iter__
method and is not a sequence, yet it is iterable. –
Lilylilyan __getitem__
for, but you do not want to allow iteration. To force this, you can implement __iter__
, but an error. In this case, isinstance(x, Iterable)
will actually cause a false positive. This may seem academic, but in PySpark, there is a Column
object implemented in exactly this way. –
Brunn try:
iterator = iter(the_element)
except TypeError:
# not iterable
else:
# iterable
# for obj in iterator:
# pass
Use the Abstract Base Classes. They need at least Python 2.6 and work only for new-style classes.
from collections.abc import Iterable # import directly from collections for Python < 3.3
if isinstance(the_element, Iterable):
# iterable
else:
# not iterable
However, iter()
is a bit more reliable as described by the documentation:
Checking
isinstance(obj, Iterable)
detects classes that are registered as Iterable or that have an__iter__()
method, but it does not detect classes that iterate with the__getitem__()
method. The only reliable way to determine whether an object is iterable is to calliter(obj)
.
isinstance(x, (collections.Iterable, collections.Sequence))
instead of iter(x)
", note that this still won't detect an iterable object that implements only __getitem__
but not __len__
. Use iter(x)
and catch the exception. –
Plankton iter(slide1)
, all goes well, however the isinstance(slide1, Iterable)
throws TypeError: issubclass() arg 1 must be a class
. –
Roomy PyUNO
Notice that your error message says issubclass()
instead of isinstance()
. –
Noncontributory isinstance()
is unreliable. You can't take a random python library, and be sure that it doesn't have a "bug" too. –
Roomy I'd like to shed a little bit more light on the interplay of iter
, __iter__
and __getitem__
and what happens behind the curtains. Armed with that knowledge, you will be able to understand why the best you can do is
try:
iter(maybe_iterable)
print('iteration will probably work')
except TypeError:
print('not iterable')
I will list the facts first and then follow up with a quick reminder of what happens when you employ a for
loop in python, followed by a discussion to illustrate the facts.
You can get an iterator from any object o
by calling iter(o)
if at least one of the following conditions holds true:
a) o
has an __iter__
method which returns an iterator object. An iterator is any object with an __iter__
and a __next__
(Python 2: next
) method.
b) o
has a __getitem__
method.
Checking for an instance of Iterable
or Sequence
, or checking for the
attribute __iter__
is not enough.
If an object o
implements only __getitem__
, but not __iter__
, iter(o)
will construct
an iterator that tries to fetch items from o
by integer index, starting at index 0. The iterator will catch any IndexError
(but no other errors) that is raised and then raises StopIteration
itself.
In the most general sense, there's no way to check whether the iterator returned by iter
is sane other than to try it out.
If an object o
implements __iter__
, the iter
function will make sure
that the object returned by __iter__
is an iterator. There is no sanity check
if an object only implements __getitem__
.
__iter__
wins. If an object o
implements both __iter__
and __getitem__
, iter(o)
will call __iter__
.
If you want to make your own objects iterable, always implement the __iter__
method.
for
loopsIn order to follow along, you need an understanding of what happens when you employ a for
loop in Python. Feel free to skip right to the next section if you already know.
When you use for item in o
for some iterable object o
, Python calls iter(o)
and expects an iterator object as the return value. An iterator is any object which implements a __next__
(or next
in Python 2) method and an __iter__
method.
By convention, the __iter__
method of an iterator should return the object itself (i.e. return self
). Python then calls next
on the iterator until StopIteration
is raised. All of this happens implicitly, but the following demonstration makes it visible:
import random
class DemoIterable(object):
def __iter__(self):
print('__iter__ called')
return DemoIterator()
class DemoIterator(object):
def __iter__(self):
return self
def __next__(self):
print('__next__ called')
r = random.randint(1, 10)
if r == 5:
print('raising StopIteration')
raise StopIteration
return r
Iteration over a DemoIterable
:
>>> di = DemoIterable()
>>> for x in di:
... print(x)
...
__iter__ called
__next__ called
9
__next__ called
8
__next__ called
10
__next__ called
3
__next__ called
10
__next__ called
raising StopIteration
On point 1 and 2: getting an iterator and unreliable checks
Consider the following class:
class BasicIterable(object):
def __getitem__(self, item):
if item == 3:
raise IndexError
return item
Calling iter
with an instance of BasicIterable
will return an iterator without any problems because BasicIterable
implements __getitem__
.
>>> b = BasicIterable()
>>> iter(b)
<iterator object at 0x7f1ab216e320>
However, it is important to note that b
does not have the __iter__
attribute and is not considered an instance of Iterable
or Sequence
:
>>> from collections import Iterable, Sequence
>>> hasattr(b, '__iter__')
False
>>> isinstance(b, Iterable)
False
>>> isinstance(b, Sequence)
False
This is why Fluent Python by Luciano Ramalho recommends calling iter
and handling the potential TypeError
as the most accurate way to check whether an object is iterable. Quoting directly from the book:
As of Python 3.4, the most accurate way to check whether an object
x
is iterable is to calliter(x)
and handle aTypeError
exception if it isn’t. This is more accurate than usingisinstance(x, abc.Iterable)
, becauseiter(x)
also considers the legacy__getitem__
method, while theIterable
ABC does not.
On point 3: Iterating over objects which only provide __getitem__
, but not __iter__
Iterating over an instance of BasicIterable
works as expected: Python
constructs an iterator that tries to fetch items by index, starting at zero, until an IndexError
is raised. The demo object's __getitem__
method simply returns the item
which was supplied as the argument to __getitem__(self, item)
by the iterator returned by iter
.
>>> b = BasicIterable()
>>> it = iter(b)
>>> next(it)
0
>>> next(it)
1
>>> next(it)
2
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Note that the iterator raises StopIteration
when it cannot return the next item and that the IndexError
which is raised for item == 3
is handled internally. This is why looping over a BasicIterable
with a for
loop works as expected:
>>> for x in b:
... print(x)
...
0
1
2
Here's another example in order to drive home the concept of how the iterator returned by iter
tries to access items by index. WrappedDict
does not inherit from dict
, which means instances won't have an __iter__
method.
class WrappedDict(object): # note: no inheritance from dict!
def __init__(self, dic):
self._dict = dic
def __getitem__(self, item):
try:
return self._dict[item] # delegate to dict.__getitem__
except KeyError:
raise IndexError
Note that calls to __getitem__
are delegated to dict.__getitem__
for which the square bracket notation is simply a shorthand.
>>> w = WrappedDict({-1: 'not printed',
... 0: 'hi', 1: 'StackOverflow', 2: '!',
... 4: 'not printed',
... 'x': 'not printed'})
>>> for x in w:
... print(x)
...
hi
StackOverflow
!
On point 4 and 5: iter
checks for an iterator when it calls __iter__
:
When iter(o)
is called for an object o
, iter
will make sure that the return value of __iter__
, if the method is present, is an iterator. This means that the returned object
must implement __next__
(or next
in Python 2) and __iter__
. iter
cannot perform any sanity checks for objects which only
provide __getitem__
, because it has no way to check whether the items of the object are accessible by integer index.
class FailIterIterable(object):
def __iter__(self):
return object() # not an iterator
class FailGetitemIterable(object):
def __getitem__(self, item):
raise Exception
Note that constructing an iterator from FailIterIterable
instances fails immediately, while constructing an iterator from FailGetItemIterable
succeeds, but will throw an Exception on the first call to __next__
.
>>> fii = FailIterIterable()
>>> iter(fii)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: iter() returned non-iterator of type 'object'
>>>
>>> fgi = FailGetitemIterable()
>>> it = iter(fgi)
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/path/iterdemo.py", line 42, in __getitem__
raise Exception
Exception
On point 6: __iter__
wins
This one is straightforward. If an object implements __iter__
and __getitem__
, iter
will call __iter__
. Consider the following class
class IterWinsDemo(object):
def __iter__(self):
return iter(['__iter__', 'wins'])
def __getitem__(self, item):
return ['__getitem__', 'wins'][item]
and the output when looping over an instance:
>>> iwd = IterWinsDemo()
>>> for x in iwd:
... print(x)
...
__iter__
wins
On point 7: your iterable classes should implement __iter__
You might ask yourself why most builtin sequences like list
implement an __iter__
method when __getitem__
would be sufficient.
class WrappedList(object): # note: no inheritance from list!
def __init__(self, lst):
self._list = lst
def __getitem__(self, item):
return self._list[item]
After all, iteration over instances of the class above, which delegates calls to __getitem__
to list.__getitem__
(using the square bracket notation), will work fine:
>>> wl = WrappedList(['A', 'B', 'C'])
>>> for x in wl:
... print(x)
...
A
B
C
The reasons your custom iterables should implement __iter__
are as follows:
__iter__
, instances will be considered iterables, and isinstance(o, collections.abc.Iterable)
will return True
.__iter__
is not an iterator, iter
will fail immediately and raise a TypeError
.__getitem__
exists for backwards compatibility reasons. Quoting again from Fluent Python:That is why any Python sequence is iterable: they all implement
__getitem__
. In fact, the standard sequences also implement__iter__
, and yours should too, because the special handling of__getitem__
exists for backward compatibility reasons and may be gone in the future (although it is not deprecated as I write this).
is_iterable
by returning True
in the try
block and False
in the except TypeError
block? –
Ultrasonic def isiterable(x): return hasattr(x, '__iter__') or hasattr(x, '__getitem__')
instead? Its a bit faster in my testing –
Davidoff 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.
typing.Dict
is considered iterable by iter(Dict)
but list(Dict)
fails with error TypeError: Parameters to generic types must be types. Got 0.
. As expected isinstance(Dict, Iterable)
returns false. –
Alcatraz iter
caused some of our code that used "pre-caching" to slow down unnecessarily. If the __iter__
code is slow, so will calling iter
... any time you just want to see if something is iterable. –
Contrary isinstance(obj, Iterable)
fails: numpy 'arrays' of individual values. If you have 'obj = np.array(int(1)), numpy will happily say
obj = array(1). The shape is an empty tuple, and
len(obj)` returns TypeError: len() of unsized object
. HOWEVER! If you write: isinstance(obj, Iterable)
you get...True
. Calamity –
Eloign Since Python 3.5 you can use the typing module from the standard library for type related things:
from typing import Iterable
...
if isinstance(my_item, Iterable):
print(True)
True
for single, string objects fyi. –
Constable typing
is for type-checking tools (e.g. MyPy, PyCharm) and doesn't guarantee this behaviour. I think you meant to import the Iterable
class from collections.abc
instead. –
Misbeliever 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".
In Python <= 2.5, you can't and shouldn't - iterable was an "informal" interface.
But since Python 2.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 desirable 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 satisfy 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 satisfy 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 infrastructure pretty useful.
except:
in the example code for beginners. It promotes bad practice. –
Compellation 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.
TypeError
and throw you off here, but basically yes. –
Hydromedusa as e
" after TypeError
instead of by adding ", e
"? –
Klemm try
is faster than if
, but except
is slower than else
. exception lookup is generally slow regardless of what language you're using. Note that this doesn't include execution between elif
and finally
, where a blank try
/finally
is generally faster than a blank if True
/else
. but be smart and use this stuff wizely, finally
is generally used for context management rather than basic logic and shouldn't be substituted due to what it ensures. –
Skipp 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?
iterable(itertools.repeat(0))
? :) –
Wetmore (x for x in a)
just creates a generator, it doesn't do any iteration on a. –
Campbellbannerman (x for x in a)
precisely equivalent to trying iterator = iter(a)
? Or there are some cases where the two are different? –
Alius for _ in a: break
more straightforward ? Is it slower ? –
Tessatessellate The best solution I've found so far:
hasattr(obj, '__contains__')
which basically checks if the object implements the in
operator.
Advantages (none of the other solutions has all three):
__iter__
)Notes:
I found a nice solution here:
isiterable = lambda obj: isinstance(obj, basestring) \
or getattr(obj, '__iter__', False)
According to the Python 2 Glossary, iterables are
all sequence types (such as
list
,str
, andtuple
) and some non-sequence types likedict
andfile
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 str
s 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.
I often find convenient, inside my scripts, to define an iterable
function.
(Now incorporates Alfe's suggested simplification):
import collections
def iterable(obj):
return isinstance(obj, collections.Iterable):
so you can test if any object is iterable in the very readable form
if iterable(obj):
# act on iterable
else:
# not iterable
as you would do with thecallable
function
EDIT: if you have numpy installed, you can simply do: from numpy import iterable
,
which is simply something like
def iterable(obj):
try: iter(obj)
except: return False
return True
If you do not have numpy, you can simply implement this code, or the one above.
if x: return True
else: return False
(with x
being boolean) you can write this as return x
. In your case return isinstance(…)
without any if
. –
Archenemy pandas has a built-in function like that:
from pandas.util.testing import isiterable
__iter__
and not really cares about sequences and similar. –
Killam It's always eluded me as to why python has callable(obj) -> bool
but not iterable(obj) -> bool
...
surely it's easier to do hasattr(obj,'__call__')
even if it is slower.
Since just about every other answer recommends using try
/except TypeError
, where testing for exceptions is generally considered bad practice among any language, here's an implementation of iterable(obj) -> bool
I've grown more fond of and use often:
For python 2's sake, I'll use a lambda just for that extra performance boost...
(in python 3 it doesn't matter what you use for defining the function, def
has roughly the same speed as lambda
)
iterable = lambda obj: hasattr(obj,'__iter__') or hasattr(obj,'__getitem__')
Note that this function executes faster for objects with __iter__
since it doesn't test for __getitem__
.
Most iterable objects should rely on __iter__
where special-case objects fall back to __getitem__
, though either is required for an object to be iterable.
(and since this is standard, it affects C objects as well)
except
is one of the slowest, most inefficient, keywords in python, it has it's purpose, but should not be used everywhere, unless you want the lowest performing code imaginable, given except
is actually invoked... try
is faster than if
, but except
is much slower than else
due to exception lookups. –
Skipp except
is the most inefficient keyword in python, and makes your execution incredibly slow if invoked often. –
Skipp try...except
blocks. Please clarify your position on that matter. –
Campbell except
is horribly inefficient due to exception lookups, however again, there are places where except
should be used where it won't be invoked often, such as within a run loop where it's known the execution will only fail 5% or so of the time, as this ensures runtime stability. However, except
should NOT be used as a substitute for conditional testing, where the especially where the failure rate is above ~30% as this will greatly slow down your runtime. –
Skipp There are a lot of ways to check if an object is iterable:
from collections.abc import Iterable
myobject = 'Roster'
if isinstance(myobject , Iterable):
print(f"{myobject } is iterable")
else:
print(f"strong text{myobject } is not iterable")
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__
.
The easiest way, respecting the Python's duck typing, is to catch the error (Python knows perfectly what does it expect from an object to become an iterator):
class A(object):
def __getitem__(self, item):
return something
class B(object):
def __iter__(self):
# Return a compliant iterator. Just an example
return iter([])
class C(object):
def __iter__(self):
# Return crap
return 1
class D(object): pass
def iterable(obj):
try:
iter(obj)
return True
except:
return False
assert iterable(A())
assert iterable(B())
assert iterable(C())
assert not iterable(D())
Notes:
__iter__
has been implemented, if the exception type is the same: anyway you will not be able to iterate the object.I think I understand your concern: How does callable
exists as a check if I could also rely on duck typing to raise an AttributeError
if __call__
is not defined for my object, but that's not the case for iterable checking?
I don't know the answer, but you can either implement the function I (and other users) gave, or just catch the exception in your code (your implementation in that part will be like the function I wrote - just ensure you isolate the iterator creation from the rest of the code so you can capture the exception and distinguish it from another TypeError
.
The isiterable
func at the following code returns True
if object is iterable. if it's not iterable returns False
def isiterable(object_):
return hasattr(type(object_), "__iter__")
example
fruits = ("apple", "banana", "peach")
isiterable(fruits) # returns True
num = 345
isiterable(num) # returns False
isiterable(str) # returns False because str type is type class and it's not iterable.
hello = "hello dude !"
isiterable(hello) # returns True because as you know string objects are iterable
In my code I used to check for non iterable objects:
hasattr(myobject,'__trunc__')
This is quite quick and can be used to check for iterables too (use not
).
I'm not 100% sure if this solution works for all objects, maybe other can give a some more background on it. __trunc__
method seams to be related to numerical types (all objects that can be rounded to integers needs it). But I didn't found any object that contains __trunc__
together with __iter__
or __getitem__
.
Instead of checking for the __iter__
attribute, you could check for the __len__
attribute, which is implemented by every python builtin iterable, including strings.
>>> hasattr(1, "__len__")
False
>>> hasattr(1.3, "__len__")
False
>>> hasattr("a", "__len__")
True
>>> hasattr([1,2,3], "__len__")
True
>>> hasattr({1,2}, "__len__")
True
>>> hasattr({"a":1}, "__len__")
True
>>> hasattr(("a", 1), "__len__")
True
None-iterable objects would not implement this for obvious reasons. However, it does not catch user-defined iterables that do not implement it, nor do generator expressions, which iter
can deal with. However, this can be done in a line, and adding a simple or
expression checking for generators would fix this problem. (Note that writing type(my_generator_expression) == generator
would throw a NameError
. Refer to this answer instead.)
You can use GeneratorType from types:
>>> import types >>> types.GeneratorType <class 'generator'> >>> gen = (i for i in range(10)) >>> isinstance(gen, types.GeneratorType) True
--- accepted answer by utdemir
(This makes it useful for checking if you can call len
on the object though.)
__len__
... for this case, it's usually the improper use of calculating distance between 2 objects. where obj.dist()
could be easily substituted. –
Skipp Not really "correct" but can serve as quick check of most common types like strings, tuples, floats, etc...
>>> '__iter__' in dir('sds')
True
>>> '__iter__' in dir(56)
False
>>> '__iter__' in dir([5,6,9,8])
True
>>> '__iter__' in dir({'jh':'ff'})
True
>>> '__iter__' in dir({'jh'})
True
>>> '__iter__' in dir(56.9865)
False
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)
'__iter__' in dir(arg)
instead of '__getitem__' in dir(arg)
–
Cavy O(n)
, since dir()
returns a list, and set(dir())
is even slower since it's performance is O(n)(1)
, due to needing to convert the list into a set before calling set.__contains__
, which is O(1)
, but as stated earlier, not all iterables use __getitem__
, thus it's unreliable. –
Skipp After experimenting with various functions, I believe this is the fastest way to reliably tell if an object is iterable, although it involves bare exceptions:
def isiterable(x):
if hasattr(x, '__iter__'): return not isinstance(x, type)
if hasattr(x, '__getitem__'):
try:
x[0]
return True
except Exception:
return False
return False
This is not a perfect solution, but as I explain below, this one is among the most reliable ones. There are two edge cases that I can think of, where this function will fail: first is an non-iterable object that has a __getitem__
method and x[0]
. Second is an iterable type
object.
If you don't want bare exceptions, there are a few methods I've described below, and they all have different drawbacks.
In order to be iterable, an object needs either __getitem__
or __iter__
methods.
However some objects with __getitem__
are not iterable, for example:
class Getitem_ButNotIterable:
def __init__(self): self.dict_ = {"a":1, "b":2}
def __getitem__(self, item:str): return self.dict_[item]
for i in Getitem_ButNotIterable():
pass
>> KeyError: 0
Worth mentioning that running iter(Getitem_ButNotIterable())
will not raise an exception unless you actually try to iterate over it, so that method isn't fully reliable.
And types can have __iter__
attribute, but they are (usually) not iterable either.
hasattr(list, "__iter__") >> True
for i in list: pass >> TypeError: 'type' object is not iterable
However types can actually be iterable, that is if their metaclass has __iter__
method.
The hardest edge case is this one:
class Getitem_ButNotIterable2:
def __init__(self): self.dict_ = {0:1, "b":2}
def __getitem__(self, item:str): return self.dict_[item]
If you try to iterate through this, it will call x[0]
which returns 0, but then it will call x[1]
which will raise an KeyError, which iter does not catch. Unfortunately I don't think there is an easy way to detect those.
To test various methods, I created 40 different iterables and non-iterables, and tried a bunch of functions for 1_000_000 times on each iterable/non-iterable, and wrote the total time everything took. Method at the beginning takes 8 seconds for reference.
from collections.abc import Iterable
def is_iterable1(x):
return isinstance(x, Iterable)
Took 17 seconds. This checks if object has an __iter__
method, therefore it will fail to recognize iterable objects that only have __getitem__
.
Worth mentioning that if you use the deprecated typing.Iterable
instead, the time goes up to 50 seconds.
def is_iterable2(x):
return hasattr(x, "__iter__")
Took 5 seconds. This is almost equivalent to isinstance(x, Iterable)
, however are is additional false positives: types, like is_iterable2(list)
will return True.
def is_iterable3(x):
return hasattr(x, "__iter__") and hasattr(x, "__getitem__")
Took 7 seconds. Unlike above two methods, this will recognize iterable __getitem__
objects, but also give false positives on non-iterable __getitem__
objects, and types with those methods
def is_iterable4(x):
try:
iter(x)
return True
except TypeError:
return False
return True
Took 36 seconds. This will produce a false positive on non-iterable objects that have __getitem__
. Changing iter(x)
to (i for i in x)
is equivalent. This is a good method if you don't want bare exceptions.
def is_iterable5(x):
try:
0 in x
except TypeError:
return False
else:
return True
Took 50 seconds. This will raise an exception on non-iterable objects that have __getitem__
.
def is_iterable6(x):
try:
for _ in x: return True
except Exception: return False
return True
Took 33 seconds. We are getting into bare exceptions now, but I can't think of any other ways to make it more reliable. There can be some edge cases where you don't want to iterate over some object outside of the usual usage, as this could mess the internal state.
def is_iterable7(x):
if hasattr(x.__class__, '__iter__'): return True
if hasattr(x, '__getitem__'):
try:
x[0]
return True
except Exception:
return False
return False
Took 23 seconds. Correctly evaluates almost everything as it won't give a true negative for types that have __iter__
method. There are still two edge cases: non-iterable object with __getitem__
, and iterable classes, ones that have a metaclass with __iter__
method. However I don't think it is possible to get easily more reliable than this,
def is_iterable8(x):
if hasattr(x, '__iter__'): return not isinstance(x, type)
if hasattr(x, '__getitem__'):
try:
x[0]
return True
except Exception:
return False
return False
Took 8 seconds. Should be equivalent to function above, so, unless you expect classes with metaclasses with __iter__
, and if you don't mind bare exceptions, this seems to be the best way to tell if an object is iterable.
Bonus:
def super_slow_but_is_almost_definitely_iterable(x):
try:
for i,v in enumerate(x):
if i == 1000:break
return True
except Exception: return False
Took just 40 seconds, surprisingly. This is the most reliable way, except it involves iterating so in some edge cases it will mess up the internal state. Also if you have objects that are expensive to iterate through, this will become extremely slow.
Maybe just write hasattr(obj, "__iter__")
Or... something like this might work:
def is_iterable(obj: object) -> bool:
return hasattr(obj, "__iter__")
© 2022 - 2024 — McMap. All rights reserved.
__getitem__
is also sufficient to make an object iterable – Bruteiter(myObj)
succeeds ifisinstance(myObj, dict)
, so if you're looking at amyObj
that could be a sequence ofdict
s or a singledict
, you'll succeed in both cases. A subtlety that is important if you want to know what's a sequence and what isn't. (in Python 2) – After__getitem__
is also sufficient to make an object iterable ... if it starts at zero index. – Helaina__getitem__
is sufficient but not necessary. E.g. a generator object is iterable but it does not have the__getitem__
. – Holbein