Python -- 生成器在迭代器之间散布值 -- 我这样做正确吗?

Python -- generator interspersing value between iterator -- am I doing this correctly?

我做了这个功能:

def iter_intersperse(iterOver, injectItem, startWithIter = True):
    for item in iterOver:
        sendItem = (item, injectItem) if startWithIter else (injectItem, item)
        yield sendItem

在生成器中的项目之间穿插项目。它用于一些 wxpython AddMany 调用,我想在大字典中的每个面板之间添加一个间隔符(所有间隔符的大小都相同)。它有效,但对于我正在尝试做的事情来说似乎有点矫枉过正,即使它很小。

我已经考虑了几个我能想到的选项 -- 创建一个与面板字典长度相同的间隔列表并压缩它们,这似乎更过分了。我想我也许可以用 set() True 或 False 等做一个切换生成器,但我想不出如何让它工作。前段时间有人告诉我,如果你曾经制作过一个迭代函数,你可能不会想到一些已经完成它的 itertool。我知道 cycle 可以做到,但它需要的代码与我上面的函数一样多。

作为参考,我只想做一个理解,但 AddMany 需要一个元组列表,所以我必须从列表或字典转换——这也似乎有点过分了。

你能想到更优雅的方式吗?或者,如果不是,上面的代码是否尽可能高效?

我相信您可以使用带有空列表和填充值的 zip_longest

from itertools import zip_longest

for x, y in zip_longest([], [1, 2, 3], fillvalue='inject'):
    print(x, y)

'inject' 1
'inject' 2
'inject' 3

如果你想压平结果,使用链

from itertools import chain, zip_longest

def iter_intersperse(iterOver, injectitem, startWithIter=True):
    args = [[], iterOver] if startWithIter else [iterOver, []]
    return chain.from_iterable(zip_longest(*args, fillvalue=injectitem))

我建议使用 zipitertools.repeat。可能这很简单,可以内联(你大概知道顺序),但如果你想在函数中使用它,你也可以这样做:

def iter_intersperse(iterOver, injectItem, startWithIter = True):
    if startWithIter:
        return zip(iterOver, itertools.repeat(injectItem))
    return zip(itertools.repeat(injectItem), iterOver)

如果您正在使用 Python 2,并且不想要 zip returns 的 list,请改用 itertools.izip

编辑:您似乎想要交替而不是元组,请尝试使用此生成器函数:

def iter_intersperse(iterOver, injectItem, startWithIter=True):
    if startWithIter:
        try:
            yield next(iterOver)
        except Stopiteration:
            return

    for item in iterOver:
        yield injectItem
        yield item

如果 startWithIterTrue,这不会在最后生成 injectItem 的最后一个副本。如果需要,请在函数末尾添加 if not startWithIter: yield injectItem。如果选择是否需要尾随填充值与是否需要前导填充值无关,您也可以考虑添加 stopWithIter 参数。

在 Python 的所有当前和过去版本中,您可以让 next 可能引发的 StopIteration 异常(如果迭代器为空)冒泡到调用者.在 PEP 479 生效后(在 3.5 和 3.6 中,如果您使用 from __future__ import generator_stop 请求它,在 3.7 中对每个人),但是, "leaking" StopIteration 将转换为 RuntimeError。因此,为了避免将来出现问题,我在生成器中使用 try/exceptreturn (这将在外部范围内引发新的 StopIteration 异常) .

得到你想要的交替并不难:

def iter_intersperse(iterOver, injectItem, startWithIter = True):
    for item in iterOver:
        sendItem = (item, injectItem) if startWithIter else (injectItem, item)
        for thing in sendItem:
            yield thing

这个版本比较干净,也更容易修改

def iter_intersperse(iterOver, injectItem, startWithIter = True):
    # Controls whether we want to start with injection
    inject = not startWithIter 
    for item in iterOver:
        if inject:
            yield injectItem
        yield item
        inject = True
    # If we want to end with an injection
    if startWithIter:
        yield injectItem