54

I can check for a next() method, but is that enough? Is there an idiomatic way?

Andrew Vasylchuk
  • 4,671
  • 2
  • 12
  • 30
Juanjo Conti
  • 28,823
  • 42
  • 111
  • 133

10 Answers10

68

In Python 2.6 or better, the designed-in idiom for such behavioral checks is a "membership check" with the abstract base class in the collections module of the standard library:

>>> import collections
>>> isinstance('ciao', collections.Iterable)
True
>>> isinstance(23, collections.Iterable)
False
>>> isinstance(xrange(23), collections.Iterable)
True

Indeed, this kind of checks is the prime design reason for the new abstract base classes (a second important one is to provide "mixin functionality" in some cases, which is why they're ABCs rather than just interfaces -- but that doesn't apply to collections.Iterable, it exists strictly to allow such checks with isinstance or issubclass). ABCs allow classes that don't actually inherit from them to be "registered" as subclasses anyway, so that such classes can be "subclasses" of the ABC for such checks; and, they can internally perform all needed checks for special methods (__iter__ in this case), so you don't have to.

If you're stuck with older releases of Python, "it's better to ask forgiveness than permission":

def isiterable(x):
  try: iter(x)
  except TypeError: return False
  else: return True

but that's not as fast and concise as the new approach.

Note that for this special case you'll often want to special-case strings (which are iterable but most application contexts want to treat as "scalars" anyway). Whatever approach you're using to check iterableness, if you need such special casing just prepend a check for isinstance(x, basestring) -- for example:

def reallyiterable(x):
  return not isinstance(x, basestring) and isinstance(x, collections.Iterable)

Edit: as pointed out in a comment, the question focuses on whether an object is an iter***ator*** rather than whether it's iter***able*** (all iterators are iterable, but not vice versa -- not all iterables are iterators). isinstance(x, collections.Iterator) is the perfectly analogous way to check for that condition specifically.

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 30
    The questions asks if an object is an iterator, not if it is iterable. So you should use collections.Iterator instead of collections.Iterable – Dave Kirby Jun 11 '10 at 17:04
  • 11
    In Python 3.8 and up, looks like `collections.Iterator` will instead be `collections.abc.Iterator`. Python 3.7 issues a deprecation warning for this: "Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working". – Daniel Waltrip May 30 '19 at 22:28
  • 2
    And iterator and being iterable are not the same thing. `[]` is iterable, but it is not an iterator. – Cerin Jun 28 '19 at 17:25
18

An object is iterable if it implements the iterator protocol.
You could check the presence of __iter__() method with:

hasattr(object,'__iter__')

in Python 2.x this approach misses str objects and other built-in sequence types like unicode, xrange, buffer. It works in Python 3.

Another way is to test it with iter method :

try:
   iter(object)
except TypeError:
   #not iterable
systempuntoout
  • 71,966
  • 47
  • 171
  • 241
10

To be an iterator an object must pass three tests:

  • obj has an __iter__ method
  • obj has a next method (or __next__ in Python 3)
  • obj.__iter__() returns obj

So, a roll-your-own test would look like:

def is_iterator(obj):
    if (
            hasattr(obj, '__iter__') and
            hasattr(obj, 'next') and      # or __next__ in Python 3
            callable(obj.__iter__) and
            obj.__iter__() is obj
        ):
        return True
    else:
        return False
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
  • 1
    Would calling `obj.__iter__()` run the risk of changing something? – Bob Stein Sep 25 '17 at 18:38
  • @BobStein-VisiBone: Only if the object is buggy. – Ethan Furman Feb 06 '18 at 16:49
  • @EthanFurman What do you mean? If `obj` is an iterator, then yes, it shouldn't change something. But if `obj` is not an iterator, `obj.__iter__()` is allowed to have side effects. – PyRulez Jan 16 '19 at 00:09
  • @PyRulez: Presumably BobStein's question was about iterators, and my answer was definitely about iterators; if the `obj` in question is not, in fact, an iterator then my answer to him doesn't apply. – Ethan Furman Jan 16 '19 at 02:32
  • 1
    @EthanFurman hmm? Wouldn't that mean you could just return true then? – PyRulez Jan 16 '19 at 02:33
  • @PyRulez: No. Please ask a new question if you're still confused. ;) – Ethan Furman Jan 16 '19 at 04:19
  • Just for clarity, it might help to distinguish between "iterable" and "iterator" here. Lists are iterable, but they are not iterators. `hasattr([1,2,3], '__next__') == False`, but `hasattr([1,2,3], '__iter__') == True` – Daniel Waltrip Feb 08 '19 at 17:31
9

There is a better method than other answers have suggested.

In Python we have two kinds of things: Iterable and Iterator. An object is Iterable if it can give you Iterator. It does so when you use iter() on it. An object is Iterator if you can use next() to sequentially browse through its elements. For example, map() returns Iterator and list is Iterable.

Here are more details.

Below code illustrates how to check for these types:

from collections.abc import Iterable, Iterator

r = [1, 2, 3]
e = map(lambda x:x, r)

print(isinstance(r, Iterator)) # False, because can't apply next
print(isinstance(e, Iterator)) # True
print(isinstance(r, Iterable)) # True, because can apply iter()
print(isinstance(e, Iterable)) # True, note iter() returns self
Shital Shah
  • 63,284
  • 17
  • 238
  • 185
6
from collections.abc import Iterator

isinstance(object, Iterator)
Yukyu
  • 69
  • 1
  • 1
  • 4
    While this code may solve the question, [including an explanation](//meta.stackexchange.com/q/114762) of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please [edit] your answer to add explanations and give an indication of what limitations and assumptions apply. –  Jun 24 '20 at 09:59
3

answer from python sourcecode doc comments:

{python install path}/Versions/3.5/lib/python3.5/types.py

# Iterators in Python aren't a matter of type but of protocol.  A large
# and changing number of builtin types implement *some* flavor of
# iterator.  Don't check the type!  Use hasattr to check for both
# "__iter__" and "__next__" attributes instead.
yoyo
  • 158
  • 2
  • 7
0

This example comes from the book Effective Python and is illustrated in this post.

An iterable produces an iterator. Any iterator is also an iterable, but produces itself as the iterator:

>>> list_iter = iter([])
>>> iter(list_iter) is list_iter
True
Tengerye
  • 1,796
  • 1
  • 23
  • 46
0

If you'd like to check if the value is iterable you can do something like:

>>> from typing import Iterable

>>> isinstance([], Iterable)
True
>>> isinstance(1, Iterable)
False

If you want to check if it's an iterator you can do something like:

>>> hasattr(obj, "__iter__") and hasattr(obj, "__next__")
True
Julian Camilleri
  • 2,975
  • 1
  • 25
  • 34
  • 1
    Funny how most answers to this questions check if the object is an iterable, rather than an iterator. It's relevant to add the reference to the more recent `typing` here, but please add the example checking for `Iterator`, since that's the actual question. – joanis Sep 21 '21 at 21:43
  • @joanis definitely, makes sense. – Julian Camilleri Sep 22 '21 at 07:15
  • Thanks for the update, but I'm surprised by the solution you chose. I just tested, and `isinstance(iter([]), Iterator)` returns `True`, while `isinstance([], Iterator)` returns `False`, so I would have expected your edit to use `from typing import Iterator` and `isinstance` with it. – joanis Sep 22 '21 at 13:09
  • I personally prefer to check explicitly for `__iter__` and `__next__` dunder methods - you can do that too.. I guess it's just preference. - I'm happy to add it too. – Julian Camilleri Sep 22 '21 at 13:59
  • Thinking about this - iter() on a non iterator will raise @joanis - not really best approach. – Julian Camilleri Sep 22 '21 at 16:30
  • 1
    I guess checking for those two dunder methods is the standard solution, and probably exactly what `isinstance(object, Iterator)` actually does under the hood, so maybe you're right, that's the best solution to keep here. – joanis Sep 22 '21 at 17:15
  • 1
    I would definitely not call `iter(object)` just to test if it's an iterator, to avoid incurring any run-time costs an unknown object's iterator creation might require. – joanis Sep 22 '21 at 17:20
  • Sounds good, glad we came to the same conclusion :D have a nice day! – Julian Camilleri Sep 22 '21 at 17:31
0
if '__iter__' in dir(object):
   #code
else:
   #code
-4

As the question is about Iterator not Iterable and considering usage of iterator, a simplest and pythonic way of doing this

iterable = [1,2]
iterator = iter(iterable)


def isIterator(obj):
    try:
        next(obj, None)
        return True
    except TypeError:
        return False

>>> isIterator(iterable)
False
>>> isIterator(iterator)
True

Yes. Checking on next() should be enough

user2390183
  • 975
  • 8
  • 17
  • If it's enough, it's also way too much! Using next() advances the iterator. Data is being thrown away for a type check! This would never be reliable in real world use cases. – Zim Oct 30 '20 at 00:08
  • @Zim Since OP mentioned, they can use next() method I dont think it was a problem. For example, I can try to iterate, based on error message if it is not an iterator then I may have logic to handle a different data type. So it is purely depends on your need. Can you elaborate real world use case you have in mind? – user2390183 Jan 03 '21 at 15:22