The Python Oracle

Detecting that an object is repeatedly iterable

--------------------------------------------------
Hire the world's top talent on demand or became one of them at Toptal: https://topt.al/25cXVn
and get $2,000 discount on your first invoice
--------------------------------------------------

Music by Eric Matyas
https://www.soundimage.org
Track title: Puzzle Island

--

Chapters
00:00 Detecting That An Object Is Repeatedly Iterable
01:09 Accepted Answer Score 7
03:17 Thank you

--

Full question
https://stackoverflow.com/questions/4044...

--

Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...

--

Tags
#python #python3x #iterator

#avk47



ACCEPTED ANSWER

Score 7


All iterators are iterables, but not all iterables are iterators.

The only requirement of an iterable is that it defines an __iter__() method which returns an iterator:

One method needs to be defined for container objects to provide iteration support:

container.__iter__()
Return an iterator object.

An iterator must follow the iterator protocol, which has two requirements:

  1. It has an __iter__() method that returns the object itself:

    iterator.__iter__()
    Return the iterator object itself.

  2. It has a __next__() method which returns the next item on each call, and, once exhausted, raises StopIteration on every subsequent call:

    Once an iterator’s __next__() method raises StopIteration, it must continue to do so on subsequent calls. Implementations that do not obey this property are deemed broken.

These requirements mean that iterators are never repeatable, and that you can always confirm that an iterable is an iterator (and therefore unrepeatable by definition) by confirming that iter(obj) is obj is True:

def is_unrepeatable(obj):
    return iter(obj) is obj

However: since the only requirement of an iterable is that iter(obj) returns some iterator, you can't prove that it is repeatable. An iterable could define an __iter__() method which returns an iterator with different behaviour each time it's called: for instance, it could return an iterator which iterates over its elements the first time it's called, but on subsequent calls, return an iterator which immediately raises StopIteration.

This behaviour would be strange (and annoying), but is not prohibited. Here's an example of a non-repeatable iterable class which is not an iterator:

class Unrepeatable:

    def __init__(self, iterable):
        self.iterable = iterable
        self.exhausted = False

    def __iter__(self):
        if self.exhausted:
            return
        else:
            self.exhausted = True
            yield from self.iterable

>>> x = Unrepeatable([1,2,3])
>>> list(x)
[1, 2, 3]
>>> list(x)
[]
>>> iter(x) is x
False
>>> 

I wouldn't hesitate to call such a "fake iterator" badly-behaved, and I can't think of a situation where you'd find one in the wild, but as demonstrated above, it is possible.