On Tue, Sep 14, 2021 at 11:44 PM Steven D'Aprano <st...@pearwood.info>
wrote:

> 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
>

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. Just like a class with a readline() method works fine in
some cases where a file is expected.


> 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.
>

No-one is arguing that an iterator that doesn't define __iter__ is great.
And the docs should continue to recommend strongly to add an __iter__
method returning self.

My only beef is with over-zealous people who might preemptively want to
reject an iterator at runtime that only has __next__; in particular "for"
and iter() have no business checking for this attribute ("for" only needs
__next__, and iter() only should check for the minimal version of the
protocol to reject things without __next__).


> 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 :-)
>

Your opinion is loud and clear. I just happen to disagree.

-- 
--Guido van Rossum (python.org/~guido)
*Pronouns: he/him **(why is my pronoun here?)*
<http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/H7XPX4VRUZGZ2ZHPYU6YFKKI6YLD73MP/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to