On Wed, 2 Mar 2022 at 19:34, Peter Otten <__pete...@web.de> wrote: > > On 02/03/2022 01:32, Rob Cliffe via Python-list wrote: > > > itertools.product returns an iterator (or iterable, I'm not sure of the > > correct technical term). > > There's a simple test: > > iter(x) is x --> True # iterator > iter(x) is x --> False # iterable
iter(x) --> TypeError # not iterable iter(x) is x --> True # iterator iter(x) is x --> False # iterable but not iterator All (non-broken) iterators are themselves iterable. By and large, itertools is full of classes which are their own iterators, so for instance itertools.product(...) is an iterator, not just an iterable. > So: > > >>> from itertools import product > >>> p = product("ab", [1, 2]) > >>> iter(p) is p # iterator > True > >>> items = [1, 2] # iterable > >>> iter(items) is items > False This is important, because: >>> items = [1,2,3,4,5,6] >>> i1 = iter(items); print(next(i1), next(i1)) 1 2 >>> i2 = iter(items); print(next(i2), next(i2), next(i2)) 1 2 3 >>> print(next(i2), next(i1)) 4 3 Every time you iterate over a list, you get the same items, but if you partially pump one of those iterators, it needs to remember where it was up to. In contrast, itertools.product is its own iterator, so you can't restart it. > Another interesting property of (finite) iterators/iterables > > list(iterable) == list(iterable) --> Generally True, but not guaranteed. There's been discussion now and then about giving a name to that property. I'm personally in favour of "reiterable", as in, "you can iterate over this more than once and get the same content". (Of course, mutating the list in between would mean you get different content, but it doesn't remember the position of the iterator.) > a = list(iterator) # whatever > b = list(iterator) # [] (*) > > (*) Kill the coder if that doesn't hold ;) I would call that a broken iterator. It used to be possible to have a generator function that could be broken in weird ways like that, but the worst of them now raise RuntimeError. Similarly, Python won't stop you from doing this, but it is absolutely horrid code, and something you really don't want to have to debug: >>> class BrokenIter: ... def __next__(self): ... return "spam" ... >>> class Broken: ... def __iter__(self): ... return BrokenIter() ... All it takes is forgetting the "def __iter__(self): return self" in the iterator, and you create something that LOOKS usable, but occasionally breaks. ChrisA -- https://mail.python.org/mailman/listinfo/python-list