map函数在3.X中运行到无限循环

时间:2018-03-07 03:48:42

标签: python dictionary iterator

我目前正在研究python中的迭代。

我遇到了以下代码。

def myzip(*args):
    iters = map(iter, args)
    while iters:
        res = [next(i) for i in iters]
        print(res)
        yield tuple(res)

list(myzip('abc', '1mnop'))

当我在3.X中运行代码时,代码会进入无限循环,并打印

['a', '1']
[]
[]
[]
...

我从作者那里得到的解释是

  

3.X map返回一次性可迭代对象而不是2.X中的列表。在3.X中,只要我们在循环内运行一次列表推导,它就会耗尽但仍然是真的(并且res将是[])   永远。

但我仍在努力了解正在发生的事情及其发生的原因。

另外,为什么变量res仅在('a', 'l')循环的第一次迭代中赋值while?为什么在第二次和第三次迭代中没有分配('b', 'm'),然后分配('c', 'n')

4 个答案:

答案 0 :(得分:16)

问题

  

但我仍在努力了解正在发生的事情及其发生的原因。

     

另外,为什么变量res仅在第一个中分配了值('a', 'l')    while循环的迭代?始终为res分配一个空列表[]    然后。为什么没有分配('b', 'm'),然后是('c', 'n')    和第三次迭代?

你在Python 2中发布的代码在Python 3中失败的原因是因为内置的map在Python 3中返回迭代器而不是列表,因为它在Python 2中做了。

当然除非你知道迭代器是什么,否则这并没有真正解释。虽然我可以深入了解迭代器究竟是什么 1 ,但是理解迭代器的重要部分是: 迭代器只能迭代一次 的。一旦迭代迭代器一次,就会耗尽。完成。你不能再使用它了。 2

当您在代码中的列表推导中迭代iters迭代器时, 然后iters已完成并且已用尽,无法再使用 < / em>的。所以基本上所有列表理解都是:

[next(i) for i in iters]

iters'a''l'获取抓取每个迭代器中的第一项),然后将它们存储在列表中。在while循环的下一次迭代中,iters无法再使用,为空。所以空列表是yield。这就是为什么在第一个列表yield中您看到'a''l',而其他后续列表为空。

最后,您的代码降级为无限循环的原因是因为迭代器对象 - 即使已经耗尽的对象 - 将在布尔上下文中计算为True

>>> 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
>>> 

解决方案

此问题的最简单解决方案是将map返回的迭代器强制转换为列表,因为list个对象支持多次迭代:

>>> 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)]
>>> 

正如@Chris_Rands所指出的那样,虽然上面的代码有效,但在Python 3+中实现自定义zip函数的更惯用的方法是:

def custom_zip(*args):
    return map(lambda *x: x, *args)

1 作为旁注,如果您想了解迭代器的深入内容,请参阅问题What exactly are Python's iterator, iterable, and iteration protocols?

2 要更全面地了解耗尽的迭代器为什么评估为True,请参阅问题How can I get generators/iterators to evaluate as False when exhausted?

答案 1 :(得分: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')))

输出

['a', '1', 'y']
['b', 'm', 'a']
['c', 'n', 'd']
[('a', '1', 'y'), ('b', 'm', 'a'), ('c', 'n', 'd')]

Christian Dean提供的理由。

答案 2 :(得分:0)

你发布的代码在Python 2中工作但在Python 3中运行的原因是因为内置映射在Python 3中返回一个迭代器,但在Python 2中返回一个列表。

答案 3 :(得分:0)

迭代器对象仅支持next()或for ...语句。 您可以在此处引用它:https://wiki.python.org/moin/Iterator

如果您希望输出为[(&#39; a&#39;,&#39; l&#39;),(&#39; b&#39;,&#39; m&#39;),. ..]你不应该写那样的代码。

顺便说一句,请检查这是否是您想要的:

def myzip(*args):
    iters = map(iter, args)
    while iters:
        res = [i for i in next(iters)]
        yield tuple(res)

list(myzip('abc', '1mnop'))

输出结果为:

[('a', 'b', 'c'), ('1', 'm', 'n', 'o', 'p')]