On Wed, Sep 15, 2021 at 3:54 PM Ethan Furman <ethan@stoneleaf.us> wrote:
Guido:
 > 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).

D'Aprano:
 > 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
 >
 > >>> for item in A():  pass
 > ...
 > Traceback (most recent call last):
 >   File "<stdin>", line 1, in <module>
 > TypeError: 'A' object is not iterable

Guido:
 > Yes, we all understand that. The reason I invoked "duck typing" is that as
 > long as you don't use the iterator in a situation where iter() is called
 > on it, it works fine.


I'm confused.

- a "broken" iterator should be usable in `for`;
- `A` is a broken iterator;

but

- `A()` is not usable in `for`.

What am I missing?

Steven's class A is the kind of class a custom sequence might return from its __iter__ method. E.g.

class S:
    def __iter__(self):
        return A()

Now this works:

for x in S(): ...

However this doesn't:

for x in iter(S()): ...

In Steven's view, A does not deserve to work in the former case: Because A is a "broken" iterator, he seems to want it rejected by the iter() call that is *implicit* in the for-loop.

Reminder about how for-loops work:

This:

for x in seq:
    <body>

translates (roughly) to this:

_it = iter(seq)
while True:
    try:
        x = next(_it)
    except StopIteration:
        break
    <body>

--
--Guido van Rossum (python.org/~guido)