Path: csiph.com!eternal-september.org!feeder.eternal-september.org!border1.nntp.ams1.giganews.com!nntp.giganews.com!newsfeed.xs4all.nl!newsfeed7.news.xs4all.nl!newsgate.cistron.nl!newsgate.news.xs4all.nl!post.news.xs4all.nl!not-for-mail Return-Path: X-Original-To: python-list@python.org Delivered-To: python-list@mail.python.org X-Spam-Status: OK 0.003 X-Spam-Evidence: '*H*': 0.99; '*S*': 0.00; 'true,': 0.04; 'cc:addr :python-list': 0.09; 'behave': 0.09; 'caveat': 0.09; 'incorrect': 0.09; 'iterate': 0.09; 'protocols,': 0.09; 'def': 0.13; 'applies': 0.15; 'interpreter': 0.15; '"some': 0.16; 'from:addr:rosuav': 0.16; 'from:name:chris angelico': 0.16; 'iterable': 0.16; 'iterable:': 0.16; 'iterated': 0.16; 'iterating': 0.16; 'iteration': 0.16; 'iterator': 0.16; 'object).': 0.16; 'sequence.': 0.16; 'trap': 0.16; 'user-defined': 0.16; 'wrote:': 0.16; 'flexibility': 0.18; 'integer': 0.18; "shouldn't": 0.18; '(in': 0.18; '>>>': 0.20; '2015': 0.20; 'cc:2**0': 0.20; 'cc:addr:python.org': 0.20; 'aug': 0.20; "aren't": 0.22; 'implicit': 0.22; 'produces': 0.22; 'thus': 0.24; 'header:In- Reply-To:1': 0.24; 'define': 0.27; 'message-id:@mail.gmail.com': 0.27; 'object,': 0.27; 'yield': 0.27; 'description,': 0.29; 'convert': 0.29; 'starts': 0.29; "i'm": 0.30; 'classes': 0.30; 'code': 0.30; 'error.': 0.31; 'post': 0.31; "can't": 0.32; 'included': 0.32; 'skip:_ 10': 0.32; 'class': 0.33; 'values.': 0.33; 'case,': 0.34; 'except': 0.34; 'membership': 0.34; 'received:google.com': 0.35; 'clear': 0.35; 'instance': 0.35; 'something': 0.35; 'expected': 0.35; 'but': 0.36; 'pm,': 0.36; 'subject:: ': 0.37; 'say': 0.37; 'thanks': 0.37; 'itself': 0.38; 'anything': 0.38; 'test': 0.39; 'still': 0.40; 'some': 0.40; 'easy': 0.60; 'your': 0.60; 'skip:u 10': 0.61; 'show': 0.62; 'here.': 0.62; 'more': 0.63; 'due': 0.65; 'brain': 0.66; 'here': 0.66; 'covers': 0.72; 'chrisa': 0.84; 'compare:': 0.84; 'worried': 0.84; 'x):': 0.84; 'to:none': 0.91; 'different.': 0.91; 'subject:membership': 0.91 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=mime-version:in-reply-to:references:date:message-id:subject:from:cc :content-type:content-transfer-encoding; bh=C0U1ov+LHHchK9oaQs6i+y2870jG/44ILXss+PdJPUM=; b=hB3443Z8coz9YTzobYFbo575K2uST7/YwQkMHysVXfDQdxpsV3anhEQpG8nC9d0nmU ngTmmF99J9L3RplPqulP7TlmbI40cdZ2raBihhZpQndxBAPrDq/TSxNGR7k85kZNA86/ S/rrfz54SIo5fFNRAIFQCHF5pnekZzecinRToHCATkvt7ZlYz/Kgst0Y27KHTo0XB0Yq aHXwtIvSg2XDtwWRRJJQUV5yRXHGXuDJ4Kqu//XiXweCtE8Kl0fuBFw485yuSyYEyb55 9ZUhJI5X7GE2XjOXCvoU3qFklkt7gAZnGKau4qrERyVMW1VcByODAXfoWY4lCdoqfm4/ gFKQ== MIME-Version: 1.0 X-Received: by 10.107.163.11 with SMTP id m11mr15994755ioe.31.1439115193149; Sun, 09 Aug 2015 03:13:13 -0700 (PDT) In-Reply-To: References: <88256581-75d4-4f77-81f0-9e3e25baecbc@googlegroups.com> Date: Sun, 9 Aug 2015 20:13:13 +1000 Subject: Re: Iterators membership testing From: Chris Angelico Cc: "python-list@python.org" Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.20+ Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Newsgroups: comp.lang.python Message-ID: Lines: 56 NNTP-Posting-Host: 2001:888:2000:d::a6 X-Trace: 1439115197 news.xs4all.nl 2851 [2001:888:2000:d::a6]:50676 X-Complaints-To: abuse@xs4all.nl Xref: csiph.com comp.lang.python:95184 On Sun, Aug 9, 2015 at 7:55 PM, Pierre Quentel w= rote: > Thanks for the explanation. I understand that an iterator can't test memb= ership any other way, but I'm still worried about how the documentation exp= lains it. Reading it, I naively expected that an iterator which produces th= e integer 0 such as the one included in my post would say that "0 in iterat= or" is True, because "some value z with 0 =3D=3D z is produced while iterat= ing over y". > > Shouldn't it be more clear if the documentation said something like : "Fo= r user-defined classes which do not define __contains__() but do define __i= ter__(), x in y consumes the iterator y until some value z with x =3D=3D z = is produced" ? > Consuming the iterator is implicit in the description, because the only way to iterate over an iterator is to call next() on it, which consumes values. But the description covers all *iterables*, and the caveat applies only to *iterators*. Compare: class A: def __init__(self, x): self.x =3D x def __iter__(self): counter =3D -1 while counter < self.x: counter +=3D 1 yield counter This is the same code as you had, except that it's an iterable that returns a _different_ object, and thus is not itself an iterator (in this case, A().__iter__() returns a generator object). Note the similarity of code to your example, and how easy it is to convert your code to be a generator and make it reiterable. Now let's do that membership test: >>> iterable =3D A(10) >>> for i in iterable: ... assert i in iterable ... No error. We can repeatedly iterate over this generator factory, and each iteration starts a new instance of the generator, which thus contains all the same values. Adding a print() will show that every number is indeed iterated over. The trap you're seeing here is that iterating over an iterator always consumes it, but mentally, you're expecting this to be iterating over a new instance of the same sequence. That's perfectly understandable, but due to the extreme flexibility of the iterator and iterable protocols, there's no way for the interpreter to say anything different. Make your object reiterable, and then it'll behave the way your brain is expecting... but the docs aren't incorrect here. ChrisA