如何改善替代迭代器的Python 3代码?

时间:2018-07-04 10:36:01

标签: python refactoring

我最近写了这个Python 3代码,该代码应该在提供给它的所有可迭代对象之间交替。也就是说,如果函数以参数(first, second, third)的形式给出,那么它将产生first[0], second[0], third[0], first[1], ...。如果second先于其他second[15], third[16], first[16], third[16], ...用完,则将其跳过: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 ,直到所有可迭代项都用尽。

是的。它是功能性的,但看起来并不十分“ pythonic”。我特别不喜欢必须保留一组标记来告诉我生成器是否为空。

{{1}}

2 个答案:

答案 0 :(得分:0)

您基本上是在重新实现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()方法不同,它不需要您测试前哨值({{ 1}}或item is not Nonenot item)。

答案 1 :(得分:-1)

您将iterableschain压缩在一起

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