如何改进替代迭代器的python3代码?

2024-06-08 20:21:28 发布

您现在位置:Python中文网/ 问答频道 /正文

我最近编写了这个python3代码,它应该在所有提供给它的iterables之间交替使用。也就是说,如果函数作为参数(first, second, third)给出,那么它将产生first[0], second[0], third[0], first[1], ...。如果second在其他项之前用完,则跳过它:second[15], third[16], first[16], third[16], ...,直到所有iterables都用完为止。你知道吗

给你。它是功能性的,但它看起来不是很“Python”。我特别不喜欢保留一系列的标志来告诉我发电机是空的。你知道吗

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

Tags: 函数代码infor参数python3manyfirst
2条回答

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

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变为0,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()方法不同,不需要您在每次迭代中测试sentinel值(item is not Nonenot itemitem is not unique_sentinel_singleton)。你知道吗

iterableschain压缩到一起

from itertools import chain, zip_longest
def zipper(*many):
    return  filter(None, chain(*zip_longest(*many)))

相关问题 更多 >