我目前正在研究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')
?
答案 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')]