Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple iterators (using enumerate) for the same iterable, what is going on?

Consider the following example:

s = 'abc'
[(c1, c2) for j, c2 in enumerate(s) for i, c1 in enumerate(s)]

Output:

[('a', 'a'),
 ('b', 'a'),
 ('c', 'a'),
 ('a', 'b'),
 ('b', 'b'),
 ('c', 'b'),
 ('a', 'c'),
 ('b', 'c'),
 ('c', 'c')]

I would expected the same output if enumerate is called outside the list comprehension and the iterators are assigned to variables:

it1, it2 = enumerate(s), enumerate(s)
[(c1, c2) for j, c2 in it1 for i, c1 in it2]

But I get:

[('a', 'a'), ('b', 'a'), ('c', 'a')]

What is going on? I use Python 3.6.9.

like image 365
Nils Avatar asked Mar 27 '20 13:03

Nils


People also ask

How do you use multiple iterators in Python?

Iterate over multiple lists at a time We can iterate over lists simultaneously in ways: zip() : In Python 3, zip returns an iterator. zip() function stops when anyone of the list of all the lists gets exhausted. In simple words, it runs till the smallest of all the lists.

Is enumerate an iterator?

In this example, you assign the return value of enumerate() to enum . enumerate() is an iterator, so attempting to access its values by index raises a TypeError . Fortunately, Python's enumerate() lets you avoid all these problems.

Which words are used for iterations in Python multiple answers correct?

These include the string, list, tuple, dict, set, and frozenset types. But these are by no means the only types that you can iterate over. Many objects that are built into Python or defined in modules are designed to be iterable.

What makes a list an iterable object What is another example of an iterable object?

Definition: An iterable is any Python object capable of returning its members one at a time, permitting it to be iterated over in a for-loop. Familiar examples of iterables include lists, tuples, and strings - any such sequence can be iterated over in a for-loop.


2 Answers

What is happening is that the inner iterator gets exhausted after the first iteration of the outer iterator:

s = 'abc'
it1 = enumerate(s)
it2 = enumerate(s)

for i, x in it1:
    for j, y in it2:  # <-- gets consumed when i = 0 and stays empty
        ...

By contrast:

s = 'abc'

for i, x in enumerate(s):
    for j, y in enumerate(s):  # <-- gets recreated at each iteration
        ....

If you need persistence, enclose it in a list or tuple:

itr = list(enumerate(s))
print([(c1, c2) for j, c2 in itr for i, c1 in itr])
# [('a', 'a'), ('b', 'a'), ('c', 'a'), ('a', 'b'), ('b', 'b'), ('c', 'b'), ('a', 'c'), ('b', 'c'), ('c', 'c')]

although note the different memory footprint of using enumerate() multiple times versus having it enclosed in a list or tuple.

like image 182
norok2 Avatar answered Oct 19 '22 23:10

norok2


The difference is that in:

s = 'abc'
[(c1, c2) for j, c2 in enumerate(s) for i, c1 in enumerate(s)]

A new c1 enumerator is created for each value yielded on the first for. While on your second example, the same enumerator is used (it2) - and it gets exausted once it reaches "c" - when the first for advances to the next iteration (c2 = "b") and tries to iterate "it2", it is already exhausted - and the whole expression ends.

like image 45
jsbueno Avatar answered Oct 20 '22 00:10

jsbueno