Python 如果未使用特定迭代器,则会失败的类zip函数

Python 如果未使用特定迭代器,则会失败的类zip函数,python,python-3.x,iterator,iterable,Python,Python 3.x,Iterator,Iterable,我想要一个类似zip的函数,如果没有使用最右边的迭代器,它就会失败。它应该屈服,直到失败 比如说 >>> a = ['a', 'b', 'c'] >>> b = [1, 2, 3, 4] >>> myzip(a, b) Traceback (most recent call last): ... ValueError: rightmost iterable was not consumed >>> list(my

我想要一个类似zip的函数,如果没有使用最右边的迭代器,它就会失败。它应该屈服,直到失败

比如说

>>> a = ['a', 'b', 'c']
>>> b = [1, 2, 3, 4]

>>> myzip(a, b)
Traceback (most recent call last):
    ...
ValueError: rightmost iterable was not consumed

>>> list(myzip(b, a))
[(1, 'a'), (2, 'b'), (3, 'c')]
也许标准库中有一个函数可以帮助实现这一点

重要提示:

在真实上下文中,迭代器不在对象之上,因此我不能只检查长度或为它们编制索引。

编辑:

这就是我到目前为止的想法

def myzip(*iterables):
    iters = [iter(i) for i in iterables]

    zipped = zip(*iters)

    try:
        next(iters[-1])
        raise ValueError('rightmost iterable was not consumed')
    except StopIteration:
        return zipped

这是最好的解决方案吗?它不会保持迭代器的状态,因为我调用了它的next,这可能是个问题。

使用来自itertools的zip_longest的其他选项。如果使用了所有列表,它也会返回true或false。也许不是最有效的方法,但可以改进:

from itertools import zip_longest
a = ['a', 'b', 'c', 'd']
b = [1, 2, 3, 4, 5]
c = ['aa', 'bb', 'cc', 'dd', 'ee', 'ff']

def myzip(*iterables):
    consumed = True
    zips = []
    for zipped in zip_longest(*iterables):
      if None in zipped:
        consumed = False 
      else:
        zips.append(zipped)
    return [zips, consumed]


list(myzip(a, b, c))
#=> [[('a', 1, 'aa'), ('b', 2, 'bb'), ('c', 3, 'cc'), ('d', 4, 'dd')], False]

我认为这是通过检查最后一个消费者在返回之前是否完全消费完成的

# Example copied from https://stackoverflow.com/questions/19151/build-a-basic-python-iterator
class Counter:
    def __init__(self, low, high):
        self.current = low
        self.high = high

    def __iter__(self):
        return self

    def __next__(self): # Python 3: def __next__(self)
        if self.current > self.high:
            raise StopIteration
        else:
            self.current += 1
            return self.current - 1

# modified from https://docs.python.org/3.5/library/functions.html#zip
def myzip(*iterables):
    sentinel = object()
    iterators = [iter(it) for it in iterables]
    while iterators:
        result = []
        for it in iterators:
            elem = next(it, sentinel)
            if elem is sentinel:
                elem = next(iterators[-1], sentinel)
                if elem is not sentinel:
                    raise ValueError("rightmost iterable was not consumed")
                else:
                    return
            result.append(elem)
        yield tuple(result)


a = Counter(1,7)
b = range(9)

for val in myzip(a,b):
    print(val)

你可以用几种不同的方法来做这件事

  • 您可以将普通的
    zip()
    与迭代器一起使用,并手动检查它是否耗尽

    def check_consumed(it):
        try:
            next(it)
        except StopIteration:
            pass
        else:
            raise ValueError('rightmost iterable was not consumed')
    
    b_it = iter(b)
    list(zip(a, b_it))
    check_consumed(b_it)
    
  • 您可以包装普通的
    zip()
    ,为您进行检查

    def myzip(a, b):
        b_it = iter(b)
        yield from zip(a, b_it)
        # Or, if you're on a Python version that doesn't have yield from:
        #for item in zip(a, b_it):
        #    yield item
        check_consumed(b_it)
    
    list(myzip(a, b))
    
  • 您可以使用
    iter()
    next()
    从头开始编写自己的
    zip()

    (此选项无代码,因为选项2在各方面都优于此选项)

  • itertools中已经有一个允许按默认值“扩展”较短的iterable

    使用该选项并检查是否出现默认值:如果出现,则为
    “未使用最右边的元素”

    输出:

    [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
    Not all items are consumed
    

    这会消耗(最坏的情况)完整的压缩列表一次进行检查,然后将其作为可编辑返回。

    请参阅问题的编辑。添加了一个“重要注释”。你能提供一个真实上下文对象的块吗?我想这是一个列表。尝试使用
    iter(['a',b',c'])
    iter([1,2,3,4])
    (显然它们只能使用一次)我不知道我是否明白了重点,但我根据您的更新进行了编辑。我不希望它在最短的时间结束,我希望它在最右边结束。有没有办法查看迭代器,这样它的状态就不会被修改。@Ross:如果你的意思是“当
    a
    有3个项目,而
    b
    有4个项目,而不消耗其中任何一个项目时,会出现错误”,那么答案是“否”,好的。即使你只是不想从
    b
    中多消费一项,我想答案仍然是“不”。但是,您可以使用
    itertools.chain()
    来“将已消费的项目添加回来”。(你不能直接把它们加回去,但是你可以创建一个新的迭代器,先给你消耗的项目,然后继续给你未消耗的项目)好的,谢谢,幸运的是我能解决这个问题。我忘记了来自的
    收益!非常好的解决方案。请注意,您为
    myzip()
    编辑的代码不起作用-只要最后一个iterable中至少有一个元素开始,它将始终为您提供
    ValueError
    。这是因为-与Python 2中不同,
    zip()
    将立即使用iterables,并返回一个列表-在Python 3中,
    zip()
    将返回一个生成器并延迟使用iterables。这意味着,在当前版本的
    myzip()
    中调用
    next()
    时,
    zip()
    尚未使用任何项目,所有迭代器仍处于最开始阶段。是的,当我尝试使用此方法时,我确实意识到了这一点,我使用的解决方案使用了
    中获得的收益。
    [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
    Not all items are consumed