All permutations from 2 lists

Chris Angelico rosuav at gmail.com
Wed Mar 2 03:56:48 EST 2022


On Wed, 2 Mar 2022 at 19:34, Peter Otten <__peter__ at 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


More information about the Python-list mailing list