Python3的内置zip功能问题

时间:2014-10-23 16:34:12

标签: python zip

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的最后一个值也可以解决这个问题。

4 个答案:

答案 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中的groupbyitertools将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