
On Tue, Sep 14, 2021 at 09:38:38PM -0700, Guido van Rossum wrote:
I don't know what I would call an object that only has __next__, apart from "broken" :-(
It's still an iterator, since it duck-types in most cases where an iterator is required (notably "for", which is the primary use case for the iteration protocols -- it's in the first sentence of PEP 234's abstract).
I don't think it duck-types as an iterator. Here's an example: class A: def __init__(self): self.items = [1, 2, 3] def __next__(self): try: return self.items.pop() except IndexError: raise StopIteration class B: def __iter__(self): return A() It's fine to iterate over B() directly, but you can't iterate over A() at all. If you try, you get a TypeError: >>> for item in A(): pass ... Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'A' object is not iterable In practice, this impacts some very common techniques. For instance, pre-calling iter() on your input. >>> x = B() >>> it = iter(x) >>> for value in it: pass ... Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'A' object is not iterable There are all sorts of reasons why one might pre-call iter(). One common one is to pre-process the first element: it = iter(obj) first = next(obj, None) for item in it: ... Another is to test for an iterable. iter(obj) will raise TypeError if obj is not a sequence, collection, iterator, iterable etc. Another is to break out of one loop and then run another: it = iter(obj) for x in it: if condition: break do_something() for x in it: something_else() I'm sure there are others I haven't thought of. I believe that iterable objects that define `__next__` but not `__iter__` are fundamentally broken. If they happen to work in some circumstances but not others, that's because the iterator protocol is relaxed enough to work with broken iterators :-) -- Steve