如何改进迭代迭代器的Python 3代码?

问题描述 投票:0回答:2

我最近编写了这个Python 3代码,它应该在给予它的所有迭代中交替。也就是说,如果函数作为参数(first, second, third)给出,那么它会产生first[0], second[0], third[0], first[1], ...。如果second在其他人之前耗尽,那么它将被跳过:second[15], third[16], first[16], third[16], ...直到所有的迭代都用完为止。

这里是。它是功能性的,但它看起来不是非常“pythonic”。我特别不喜欢必须保留一组标志,告诉我发电机是否为空。

def zipper(*many):
    iterators = [iter(x) for x in many]
    iHasItems = [True]*len(iterators)
    while any(iHasItems):
        for n, iterator in enumerate(iterators):
            if iHasItems[n]:
                try:
                    yield next(iterator)
                except StopIteration:
                    iHasItems[n] = False
python refactoring
2个回答
0
投票

您基本上重新实现了itertools文档roundrobin()中记录的recipes section函数:

from itertools import cycle, islice

def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    # Recipe credited to George Sakkis
    num_active = len(iterables)
    nexts = cycle(iter(it).__next__ for it in iterables)
    while num_active:
        try:
            for next in nexts:
                yield next()
        except StopIteration:
            # Remove the iterator we just exhausted from the cycle.
            num_active -= 1
            nexts = cycle(islice(nexts, num_active))

每次引发StopIteration异常时,它会循环遍历迭代器并切掉最后一个;最后一个迭代器总是那个刚用尽的。

具体来说,对于输入示例,nexts在这些位置开始作为<iter('ABC'), iter('D'), iter('EF')>的循环列表,num_active3,然后算法继续进行:

  1. 屈服于A并离开<iter('D'), iter('EF'), iter('BC')>
  2. 屈服于D并离开<iter('EF'), iter('BC'), <iter('')>
  3. 屈服于E并离开<iter('BC'), <iter(''), iter('F')>
  4. 屈服于B并离开<iter(''), iter('F'), iter('C')>
  5. 试图屈服但却击中了StopIteration例外;然后循环在<iter('F'), iter('C'), iter(*stopped*)>,所以num_active变成2cycle(islice(nexts, 2))将循环设置为<iter('F'), iter('C')>while循环继续
  6. 屈服于F并离开<iter('C'), iter('')>
  7. 屈服于C并离开<iter(''), iter('')>

之后最后两个空迭代器触发进一步的StopIteration异常,并且num_active从2变为1并且while循环结束。

您可以使用collections.deque()对象和手动旋转来实现相同的功能:

from collections import deque

def roundrobin(*iterables):
    nexts = deque((iter(it).__next__ for it in iterables))
    while nexts:
        try:
            yield nexts[0]()
            # rotate the queue to the left for the next step
            nexts.rotate(-1)
        except StopIteration:
            # Remove the iterator we just exhausted from the queue
            nexts.popleft()

但这种方法比cycle变体慢,因为轮换是“手动”完成的,每次迭代都会产生成本,超过了更简单的“耗尽”异常案例实现。

与您的方法一样,这可以节省您不得不反复尝试迭代任何已经耗尽的迭代器,并且与其他人发布的zip_longest()方法不同,不需要您在每次迭代时测试标记值(item is not Nonenot itemitem is not unique_sentinel_singleton)。


-1
投票

你将iterableschain拉在一起

from itertools import chain, zip_longest
def zipper(*many):
    return  filter(None, chain(*zip_longest(*many)))
© www.soinside.com 2019 - 2024. All rights reserved.