我遇到了这段代码:
def myzip(*args):
iters = map(iter, args)
while iters:
res = [next(i) for i in iters]
yield tuple(res)
我不确定:
while iters
如何运作,我已尝试过:
x=[1,2]
x=iter(x)
if x:
print("Still True")
next(x)
next(x)
if x:
print("Still True")
并且在两种情况下仍会打印"Still True"
。
该代码的作者也说过,因为map返回了一次性的可迭代的"在3.X和"一旦我们在循环中运行列表理解一次,它们就会耗尽但仍然是真的(并且res将永远是[])"。如果我们使用3.X,他建议使用list(map(iters, args)
。
我不确定此更改实际上如何帮助它工作,因为我认为即使迭代器位于StopIteration
点,它仍然是True
(基于我之前尝试过的) 。
编辑:
作者以此为例
>>> list(myzip('abc', 'lmnop'))
[('a', 'l'), ('b', 'm'), ('c', 'n')]
答案 0 :(得分:7)
问题有几个方面。
map
返回list
,而while iters
只是确保代码没有进入循环,以防没有*args
传入功能。这是因为空列表被视为False
,非空列表被视为True
。
如果没有*args
,它就不会进入循环并隐式return
s然后会引发StopIteration
。
如果至少有一个参数,while iters
等同于while True
,它依赖于其中一个迭代器,在用尽之后引发StopIteration
。 StopIteration并不需要被捕获(至少在Python 3.7之前),因为你希望myzip
在一个可迭代用尽时停止。
在Python 3中,map
会返回map
个实例,该实例始终会被视为True
,因此while
循环等同于while True
。
但是,python-3.x中存在一个问题:迭代map
实例后,它将耗尽。在第一次迭代(while
循环)中按预期工作,但在下一次迭代中map
将为空,它将只创建一个空列表:
>>> it = map(iter, ([1,2,3], [3,4,5]))
>>> [next(sub) for sub in it]
[1, 3]
>>> [next(sub) for sub in it]
[]
没有任何东西可以引发StopIteration
,因此它将进入无限循环并永远返回空tuple
。如果while
- 列表为空,那也是您不想进入iters
循环的原因!
可以使用以下方法修复(如上所述)
iters = list(map(iter, args))
只是说明如何更有意义:
def myzip(*args):
if not args:
return
iters = [iter(arg) for arg in args] # or list(map(iter, args))
while True:
res = [next(i) for i in iters]
yield tuple(res)
如果您希望代码符合python-3.7(感谢@Kevin指出这一点),您明确需要捕获StopIteration
。有关更多信息,请参阅PEP-479:
def myzip(*args):
if not args:
return
iters = [iter(arg) for arg in args] # or list(map(iter, args))
while True:
try:
res = [next(i) for i in iters]
except StopIteration:
# the StopIteration raised by next has to be catched in python-3.7+
return
yield tuple(res)
后一个代码也适用于python-2.7和python-3.x< 3.7但它只需要捕获python 3.7 +中的StopIteration
答案 1 :(得分:4)
为什么列表理解不需要捕获StopIteration
此循环的整个点用于next()
引发StopIteration
并且列表理解不会捕获它。这就是这个生成器退出的方式; while
循环基本上是无穷无尽的,它测试的条件是稳定的。
这样,当其中一个输入耗尽时,发电机会整齐地停止:
>>> g = myzip([1], [2, 3])
>>> next(g)
(1, 2)
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in myzip
StopIteration
在StopIteration
的第一个迭代器上next()
调用引发了iters
异常。
从本质上讲,如果您在没有输入的情况下调用while iters:
,那么False
所做的就是返回myzip()
:
>>> list(myzip())
[]
否则,它也可以使用while True:
来表示所有差异。
现在,在Python 3中,map(iter, args)
返回一个迭代器,而不是一个列表。该对象只能迭代一次,但不被视为空并且总是具有 true 的布尔值。这意味着第二次浏览while iters:
循环iters
为真,但[next(i) for i in iters]
迭代0次,next()
永远不会被调用,所需StopIteration
从不提出退出发电机。这就是为什么作者建议list(map(iter, args))
作为解决办法,将map
迭代器值捕获到列表中。