enumerate build-in function returns an iterator, once the elements are exhausted it stops
in your first version, you are building a new enumerate(s) for each iteration of the first loop and in the second loop you are consuming it,
in your second version, it2 has finished his elements from the first iteration of the first loop
s = 'abc'
def my_enumerate(s, name):
print('a new enumerate: ', name)
for i in enumerate(s):
yield i
print(f'the enumerate {name} is exhausted ')
[(c1, c2) for j, c2 in my_enumerate(s, 'it1') for i, c1 in my_enumerate(s, 'it2')]
output:
a new enumerate: it1
a new enumerate: it2
the enumerate it2 is exhausted
a new enumerate: it2
the enumerate it2 is exhausted
a new enumerate: it2
the enumerate it2 is exhausted
the enumerate it1 is exhausted
[('a', 'a'),
('b', 'a'),
('c', 'a'),
('a', 'b'),
('b', 'b'),
('c', 'b'),
('a', 'c'),
('b', 'c'),
('c', 'c')]
and for:
it1, it2 = my_enumerate(s, 'it1'), my_enumerate(s, 'it2')
[(c1, c2) for j, c2 in it1 for i, c1 in it2]
output:
a new enumerate: it1
a new enumerate: it2
the enumerate it2 is exhausted
the enumerate it1 is exhausted
[('a', 'a'), ('b', 'a'), ('c', 'a')]