Iterators membership testing

Pierre Quentel pierre.quentel at gmail.com
Sun Aug 9 05:55:59 EDT 2015


Le dimanche 9 août 2015 11:25:17 UTC+2, Chris Angelico a écrit :
> On Sun, Aug 9, 2015 at 7:06 PM, Pierre Quentel <pierre.quentel at gmail.com> wrote:
> > "For user-defined classes which do not define __contains__() but do define
> > __iter__(), x in y is true if some value z with x == z is produced while
> > iterating over y. If an exception is raised during the iteration, it is as if
> > in raised that exception."
> >
> > ...
> > I get an assertion error. Setting a trace on __next__ suggests that for
> > membership testing, the interpreter consumes the iterator until the searched
> > value is found (or until exhaustion), then it resumes iteration at this point.
> 
> That's exactly right. The only way for the interpreter to handle 'in'
> on an iterator is something like this:
> 
> def contains(iter, obj):
>     for val in iter:
>         if val == obj: return True
>     return False
> 
> That's what the docs describe. So what you have is something like this:
> 
> for i in iterator:
>     for j in iterator:
>         if i == j: break
>     else:
>         assert False, '%s not found' %i
> 
> You're dragging values from the same iterator, so you're consuming it
> as part of your membership test. You can do this kind of thing:
> 
> >>> 5 in A(10)
> True
> 
> but if you've already consumed a few values, those won't be in the
> iterator any more:
> 
> >>> x = A(10)
> >>> next(x)
> 0
> >>> next(x)
> 1
> >>> next(x)
> 2
> >>> next(x)
> 3
> >>> 2 in x
> False
> 
> This is simply how iterators work. They're very different from
> repeatable iterables like lists or range objects, where you _can_ test
> for membership that way:
> 
> >>> x = [10,20,30]
> >>> for i in x: assert i in x
> ...
> >>> x = iter([10,20,30])
> >>> for i in x: assert i in x
> ...
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
> AssertionError
> 
> Note that I _could_ create a list that would pass this assertion,
> simply by duplicating every value:
> 
> >>> x = iter([10,10,20,20,30,30])
> >>> for i in x: assert i in x
> ...
> 
> But it's iterating only three times here, and the 'in' check is
> consuming the other three values. Once your A(10) has yielded some
> value, it will never yield it again, so the assertion can never pass.
> 
> Does that explain matters?
> 
> ChrisA

Thanks for the explanation. I understand that an iterator can't test membership any other way, but I'm still worried about how the documentation explains it. Reading it, I naively expected that an iterator which produces the integer 0 such as the one included in my post would say that "0 in iterator" is True, because "some value z with 0 == z is produced while iterating over y".

Shouldn't it be more clear if the documentation said something like : "For user-defined classes which do not define __contains__() but do define __iter__(), x in y consumes the iterator y until some value z with x == z is produced" ?



More information about the Python-list mailing list