map function run into infinite loop in 3.X
--
Track title: CC P Beethoven - Piano Sonata No 2 in A
--
Chapters
00:00 Question
01:14 Accepted answer (Score 16)
04:36 Answer 2 (Score 3)
05:03 Answer 3 (Score 0)
05:43 Answer 4 (Score 0)
06:02 Thank you
--
Full question
https://stackoverflow.com/questions/4914...
Accepted answer links:
[What exactly are Python's iterator, iterable, and iteration protocols?]: https://stackoverflow.com/questions/9884...
[How can I get generators/iterators to evaluate as False when exhausted?]: https://stackoverflow.com/questions/7976...
Answer 3 links:
https://wiki.python.org/moin/Iterator
--
Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...
--
Tags
#python #dictionary #iterator
#avk47
ACCEPTED ANSWER
Score 16
Problem
But I am still struggling to understand what is happening and why it is happening.
And also, why is variable
resonly assigned value('a', 'l')in the first iteration ofwhileloop?resis always assigned an empty list[]afterwards. Why is it not assigned('b', 'm'), and then('c', 'n')in second and third iteration?
The reason the code you posted works in Python 2 while failing in Python 3, is because the built-in map returns an iterator in Python 3, rather than a list as it did in Python 2.
Of course this doesn't really explain much unless you know what an iterator is. Although I could go in-depth about what an iterator is exactly1, the important part of iterators to understand here, is that: An iterator can only be iterated over once. Once you've iterated over an iterator once, it's exhausted. It's done. You can't use it anymore.2
When you iterate over the iters iterator in the list comprehension in your code, then iters is done and exhausted, and can no longer be used. So essentially all the list comprehension:
[next(i) for i in iters]
does is grab the first item from each iterator in iters (which are 'a' and 'l'), and then store those in a list. On the next iteration of your while loop, iters can no longer be used, its empty. So empty list are yielded. That's why in the first list yielded you see 'a' and 'l', while other subsequent list are empty.
Lastly, the reason you're code degrades into an infinite loop, is because of the fact that an iterator object - even one that's been exhausted - will evaluate to True in a boolean context:
>>> it = map(str, [1, 2])
>>> next(it)
'1'
>>> next(it)
'2'
>>> # The `it` iterator is exhausted
>>> next(it)
Traceback (most recent call last):
  File "<pyshell#17>", line 1, in <module>
    next(it)
StopIteration
>>> bool(it) # but it still evaluates to `True` in a boolean context
True
>>> 
Solution
The simplest solution to this problem is to cast the iterator returned by map into a list, since list objects support being iterated over multiple times:
>>> def custom_zip(*args):
    iters = list(map(iter, args))
    while iters:
        yield tuple([next(it) for it in iters])
>>> list(custom_zip('abc', [1, 2, 3]))
[('a', 1), ('b', 2), ('c', 3)]
>>> list(custom_zip('def', [4, 5, 6]))
[('d', 4), ('e', 5), ('f', 6)]
>>> list(custom_zip([1, 2, 3], [1, 4, 9], [1, 8, 27]))
[(1, 1, 1), (2, 4, 8), (3, 9, 27)]
>>> 
As @Chris_Rands also noted, although the above code works, a more idiomatic way to implement a custom zip function in Python 3+ would be:
def custom_zip(*args):
    return map(lambda *x: x, *args)
1As a side note, if you would like to understand what an iterator is in-depth, see the question What exactly are Python's iterator, iterable, and iteration protocols?
2For a more complete look into why exhausted iterators evaluate to True, see the question How can I get generators/iterators to evaluate as False when exhausted?
ANSWER 2
Score 3
def myzip(*args):
    iters = list(map(iter,args))
    while iters :
        res = [next(i) for i in iters]
        print(res)
        yield tuple(res)
print (list(myzip('abc', '1mnop','yada')))
Output
['a', '1', 'y']
['b', 'm', 'a']
['c', 'n', 'd']
[('a', '1', 'y'), ('b', 'm', 'a'), ('c', 'n', 'd')]
Reason as provided by Christian Dean.
ANSWER 3
Score 0
the reason the code you posted works in Python 2 but mot in Python 3, is because the built-in map returns an iterator in Python 3, but returns a list in Python 2.
ANSWER 4
Score 0
The iterator object only support next() or for ... statement. You can reference it from here: https://wiki.python.org/moin/Iterator
If you want output as [('a','l'), ('b','m'), ...] you should not write the code like that.
By the way, please check if this is what you want:
def myzip(*args):
    iters = map(iter, args)
    while iters:
        res = [i for i in next(iters)]
        yield tuple(res)
list(myzip('abc', '1mnop'))
And the output would be:
[('a', 'b', 'c'), ('1', 'm', 'n', 'o', 'p')]