Python zip():检查哪个可迭代对象已耗尽

5
在Python 3中,zip(*iterables)文档起会返回一个元组的迭代器。每个元组包含传入的参数序列或可迭代对象中的第i个元素。当输入的最短可迭代对象用尽时,迭代器停止。例如,我正在运行:
for x in zip(a,b):
  f(x)

有没有办法找出哪个可迭代对象,a 还是 b,导致了 zip 迭代器的停止?
假设 len() 不可靠,并且迭代两个对象以检查它们的长度不可行。

3
你打算如何处理这个结果?这可能是一个“XY问题”(指的是解决问题时提出的解决方案实际上并不是最优的解决方案)。例如,你可能最好使用itertools.zip_longest而不是zip - wjandrea
顺便说一句,欢迎来到SO!请查看[tour],如果您需要提示,请[ask]。 - wjandrea
@wjandrea 谢谢!我的意图是确保两个迭代器中的第一个被耗尽,并在另一种情况下引发异常。一个潜在的解决方法可能是仅遍历a并在循环内获取next(b),但我不确定如果a较短是否完全相同。 - Kraken
4个回答

2
我找到了以下解决方案,它使用 for 循环代替了 zip,只在第一个可迭代对象上进行循环,并在循环内部对第二个对象进行迭代。
ib = iter(b)

for r in a:
    try:
        s = next(ib)
    except StopIteration:
        print('Only b exhausted.')
        break
    print((r,s))
else: 
    try:
        s = next(ib)
        print('Only a exhausted.')
    except StopIteration:
        print('a and b exhausted.')

这里的ib = iter(b)保证了如果b是一个序列或生成器对象也能正常工作。从问题中,print((r,s))将被替换为f(x)。请保留HTML标签。

0
如果您只有两个可迭代对象,您可以使用以下代码。 exhausted [0] 将包含指示哪个迭代器已耗尽的值。 None 的值表示两者都已耗尽。
但是我必须说,我不同意 len() 不可靠的说法。实际上,您应该依赖于 len() 调用来确定答案。(除非您告诉我们您无法这样做的原因。)
def f(val):
    print(val)

def manual_iter(a,b, exhausted):
    iters = [iter(it) for it in [a,b]]
    iter_map = {}
    iter_map[iters[0]] = 'first'
    iter_map[iters[1]] = 'second'

    while 1:
        values = []
        for i, it in enumerate(iters):
            try:
                value = next(it)
            except StopIteration:
                if i == 0:
                    try:
                        next(iters[1])
                    except StopIteration:
                        return None
                exhausted.append(iter_map[it])
                return iter_map[it]
            values.append(value)
        yield tuple(values)

if __name__ == '__main__':
    exhausted = []
    a = [1,2,3]
    b = [10,20,30]
    for x in manual_iter(a,b, exhausted):
        f(x)
    print(exhausted)

    exhausted = []
    a = [1,2,3,4]
    b = [10,20,30]
    for x in manual_iter(a,b, exhausted):
        f(x)
    print(exhausted)

    exhausted = []
    a = [1,2,3]
    b = [10,20,30,40]
    for x in manual_iter(a,b, exhausted):
        f(x)
    print(exhausted)            

0

我认为Jan的回答是最好的。基本上,您想要单独处理来自zip的最后一次迭代。

import itertools as it

a = (x for x in range(5))
b = (x for x in range(3))

iterables = ((it.chain(g,[f"generator {i} was exhausted"]) for i,g in enumerate([a,b])))

for i, j in zip(*iterables):
    print(i, j)

# 0 0
# 1 1
# 2 2
# 3 generator 1 was exhausted

-3
请看下面由我编写的函数zzip(),它将实现您想要的功能。它使用了itertools模块中的zip_longest方法,并返回一个元组,其中包括zip返回的内容以及一个索引列表,如果不为空,则显示在哪个基于0的位置上迭代器/可迭代对象耗尽了,而其他迭代器/可迭代对象没有耗尽:

def zzip(*args): 
    """ Returns a tuple with the result of zip(*args) as list and a list 
    with ZERO-based indices of iterables passed to zzip which got 
    exhausted before other ones. """
    from itertools import zip_longest
    nanNANaN = 'nanNANaN'
    Zipped  = list(zip_longest(*args, fillvalue=nanNANaN))
    ZippedT = list(zip(*Zipped))
    Indx_exhausted = []
    indx_nanNANaN  = None
    for i in range(len(args)):
        try: # gives ValueError if nanNANaN is not in the column
            indx_nanNANaN = ZippedT[i].index(nanNANaN)
            Indx_exhausted += [(indx_nanNANaN, i)]
        except ValueError:
            pass 
    if Indx_exhausted: # list not empty, iterables were not same length
        Indx_exhausted.sort()
        min_indx_nanNANaN = Indx_exhausted[0][0] 
        Indx_exhausted = [ 
            i for n, i in Indx_exhausted if n == min_indx_nanNANaN ]
        return (Zipped[:min_indx_nanNANaN], Indx_exhausted)
    else: 
        return (Zipped, Indx_exhausted)

assert zzip(iter([1,2,3]),[4,5],iter([6]))  ==([(1,4,6)],[2])
assert zzip(iter([1,2]),[3,4,5],iter([6,7]))==([(1,3,6),(2,4,7)],[0,2])
assert zzip([1,2],[3,4],[5,6])              ==([(1,3,5),(2,4,6)],[])

上述代码在使用的测试用例中没有引发断言错误。

请注意,函数中的“for循环”循环遍历传递的参数列表的项目,而不是传递的可迭代元素。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接