我正在学习Effective Python(第二版)中的Item 76,我遇到了一个我不理解的案例。具体来说,我不明白为什么zip
消耗了它的第一个参数的一个额外元素。考虑下面的代码:
l1 = []
l2 = [1]
l1_it = iter(l1)
l2_it = iter(l2)
test_it = zip(l2_it, l1_it)
_ = list(test_it):
try:
next(l2_it)
except StopIteration:
print('This should not happen')
这实际上打印了This should not happen
,我发现这非常令人惊讶。我希望zip
将其第一个参数留在仍然有一个元素要检索的状态。事实是,如果我使用zip(l1_it, l2_it)
(也就是说,最短的列表是第一个),那么我确实可以调用next(l2_it)
而不会触发异常。
这是预期的吗?
zip
取最短可迭代对象的长度并限制在此范围内。由于l1
没有条目,因此l2
中的条目将不被处理。
然而…对于可迭代对象,Python不知道有多少项可用,所以它唯一的办法就是尝试获取一个项。如果没有项,它会得到一个异常。如果有一个元素,则它现在已经从迭代器中消耗了它。
也许你期待zip_longest的行为?https://docs.python.org/3/library/itertools.html?highlight=itertools itertools.zip_longest
说明iter()
的行为:
>>> x = [1]
>>> x_iter = iter(x)
# We cannot get the length and unless we are prepared to fetch it,
# we cannot check if there are items available.
>>> len(x_iter)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: object of type 'list_iterator' has no len()
# First item can be fetched
>>> next(x_iter)
1
# There is no second item so Python raises a StopIteration
>>> next(x_iter)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
我确实有这个问题,在阅读了这个和答案后,我明白了,为什么会发生这种情况,并简单地改变了压缩可迭代对象的顺序。一个在另一个(较短的可迭代对象)之前结束,然后是不应该被"吃掉"的那个;从一个(第一个视图)'unexpected'附加元素"作为第二,问题解决了。
所以基本上作为结论在我看来,人们应该记住总是把较短的iterable作为第一个参数,把较长的iterable作为第二个参数,以避免"surprise "
初版使用类似的例子:
>>> l1 = [1] # the shorter one for left side
>>> l2 = [2, 3] # the longer one for right side
>>> l1_it = iter (l1)
>>> l2_it = iter (l2)
>>> test_it = zip (l1_it, l2_it) # zip now has stop iteration
# from l1_it and leaves l2_it
# untouched further
>>> print (list (test_it)) # go through test_it with list
[(1, 2)] # and print it
>>> next (l2_it) # as you can see here there is
3 # still a next (now as expected)