Python 3.4.2 (default, Oct 8 2014, 13:44:52)
[GCC 4.9.1 20140903 (prerelease)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> gen = (x for x in range(10)) ## Need to wrap range into ()'s to create a generator, next(range(10)) is invalid
>>> list(zip(gen, [1,2,3])) ## zip will "eat up" the number 3
[(0, 1), (1, 2), (2, 3)]
>>> next(gen) ## Here i need next to return 3
4
>>>
问题是我在拉链电话后丢失了一个值。如果gen不是纯粹的代码,这将是一个更大的问题。
我不知道是否可以创建一个行为类似的函数,如果zip函数的一个参数只是一个生成器而其余的都是“普通”迭代器,那么它肯定是可能的。值已知,并存储在内存中。如果是这种情况,您可以最后检查发电机。
基本上我想知道的是,如果python标准库中有任何函数,就像我在这种情况下需要的那样。
当然,在一些案例中,人们可以做一些像
这样的事情xs = list(gen)
然后你只需要处理一个清单。
我还可以补充一点,从gen获取zip的最后一个值也可以解决这个问题。
答案 0 :(得分:4)
不,没有内置函数可以避免此行为。
会发生的是zip()
函数尝试获取所有输入的下一个值,以便它可以生成下一个元组。它必须以 a 顺序执行此操作,并且该顺序与传入的参数相同是合乎逻辑的。事实上,order is guaranteed by the documentation:
保证了迭代物的从左到右的评估顺序
因为函数需要支持任意迭代,zip()
不会尝试确定所有参数的长度。 不知道你的第二个参数只有3个元素。它只是尝试获取每个参数的下一个值,构建一个元组并返回它。如果任何参数无法生成下一个值,则zip()
迭代器完成。但确实意味着在询问列表之前,它会首先向您的生成器询问下一个元素。
除了改变输入的顺序之外,您可以构建自己的zip()
功能, 尝试将长度考虑在内,如果可用的话:
def limited_zip(*iterables):
minlength = float('inf')
for it in iterables:
try:
if len(it) < minlength:
minlength = len(it)
except TypeError:
pass
iterators = [iter(it) for it in iterables]
count = 0
while iterators and count < minlength:
yield tuple(map(next, iterators))
count += 1
所以这个版本的zip()
函数试图在传入的任何序列的最小长度上获得一个珠子。这样做不保护你不要在混合中使用更短的迭代,但确实适用于您的测试用例:
演示:
>>> gen = iter(range(10))
>>> list(limited_zip(gen, [1, 2, 3]))
[(0, 1), (1, 2), (2, 3)]
>>> next(gen)
3
答案 1 :(得分:2)
问题是zip(gen,[1,2,3])
生成0,1,2,和3也,但发现第二个参数仅为长度3。因此,如果您反向执行,则可以在 next(gen)代码行中生成3:
>>> gen = (x for x in range(10))
>>> list(zip([1,2,3],gen))
[(1, 0), (2, 1), (3, 2)]
>>> next(gen)
3
答案 2 :(得分:1)
问题是当zip
在其中一个iterables上达到StopIteration
时,它会忘记从前面的迭代中返回的值。
这是一个解决方案,使用zip_longest
中的groupby
和itertools
将zip序列划分为最短迭代终止之前和之后:
>>> from itertools import zip_longest, groupby
>>> sentinel = object()
>>> gen = (x for x in range(10))
>>> g = iter(groupby(zip_longest(gen, [1,2,3], fillvalue=sentinel),
... lambda t: sentinel not in t))
>>> _, before = next(g)
>>> list(before)
[(0, 1), (1, 2), (2, 3)]
>>> _, after = next(g)
>>> next(after)
(3, <object object at 0x7fad64cbf080>)
>>> next(gen)
4
答案 3 :(得分:1)
您可以在生成器周围使用包装类,以便访问最新的元素。我从{@ 3}}的Python Wiki中获取了大部分代码。
class gen_wrap(object):
def __init__(self, gen):
self.gen = gen
self.current = None
def __iter__(self):
return self
# Python 3 compatibility
def __next__(self):
return self.next()
def next(self):
self.current = next(self.gen)
return self.current
def last(self):
return self.current
>>> gen = gen_wrap(x for x in range(10))
>>> list(zip(gen, [1,2,3]))
[(0, 1), (1, 2), (2, 3)]
>>> gen.last()
3