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))
我建议使用 zip
和 itertools.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
如果 startWithIter
是 True
,这不会在最后生成 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
/except
对 return
(这将在外部范围内引发新的 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
我做了这个功能:
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))
我建议使用 zip
和 itertools.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
如果 startWithIter
是 True
,这不会在最后生成 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
/except
对 return
(这将在外部范围内引发新的 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