如何防止迭代器耗尽? [英] How to prevent iterator getting exhausted?

查看:28
本文介绍了如何防止迭代器耗尽?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果我创建两个列表并压缩它们

a=[1,2,3]b=[7,8,9]z=zip(a,b)

然后我将 z 输入到两个列表中

l1=list(z)l2=列表(z)

那么l1的内容就可以了[(1,7),(2,8),(3,9)],但是l2的内容就是[].

我想这是 python 对可迭代对象的一般行为.但是作为一个从 C 家族迁移过来的新手程序员,这对我来说没有意义.为什么它的行为如此?有没有办法解决这个问题?

我的意思是,是的,在这个特定的例子中,我可以将 l1 复制到 l2,但一般来说,有没有办法在我迭代一次后重置"Python 用来迭代z"的任何东西?

解决方案

没有办法重置"生成器.但是,您可以使用 itertools.tee 来复制"迭代器.

<预><代码>>>>z = zip(a, b)>>>zip1, zip2 = itertools.tee(z)>>>列表(zip1)[(1, 7), (2, 8), (3, 9)]>>>列表(zip2)[(1, 7), (2, 8), (3, 9)]

这涉及缓存值,因此只有当您以大致相同的速率迭代两个可迭代对象时才有意义.(换句话说,不要像我这里那样使用它!)

另一种方法是传递生成器函数,并在需要迭代时调用它.

def gen(x):对于范围(x)中的我:产量 i ** 2def make_two_lists(gen):返回列表(gen()),列表(gen())

但是现在你必须在传递它时将参数绑定到生成器函数.您可以使用 lambda 来实现这一点,但是很多人发现 lambda 很丑.(虽然不是我!YMMV.)

<预><代码>>>>make_two_lists(lambda: gen(10))([0, 1, 4, 9, 16, 25, 36, 49, 64, 81], [0, 1, 4, 9, 16, 25, 36, 49, 64, 81])

我希望不用说,在大多数情况下,最好只是列出并复制它.

此外,作为解释此行为的更一般方式,请考虑这一点.生成器的重点是生成一系列值,同时在迭代之间保持某种状态.现在,有时,您可能想做这样的事情,而不是简单地迭代生成器:

z = zip(a, b)而 some_condition():fst = 下一个(z,无)snd = 下一个(z,无)do_some_things(fst, snd)如果 fst 是 None 并且 snd 是 None:do_some_other_things()

假设这个循环可能可能不会耗尽z.现在我们有一个处于不确定状态的生成器!因此,在这一点上,以明确定义的方式限制生成器的行为非常重要.尽管我们不知道生成器在其输出中的位置,但我们知道 a) 所有后续访问都将在系列中产生 later 值,并且 b) 一旦它为空",我们已经得到系列中的所有项目恰好一次.我们操纵 z 状态的能力越强,就越难对其进行推理,因此我们最好避免违反这两个承诺的情况.

当然,正如 Joel Cornett 在下面指出的那样, 可以编写一个通过 send 方法接受消息的生成器;并且可以编写一个可以使用 send 重置的生成器.但请注意,在这种情况下,我们所能做的就是发送消息.我们不能直接操纵生成器的状态,因此对生成器状态的所有更改都是明确定义的(由生成器本身 - 假设它被正确编写!).send 真的是为了实现 coroutines,所以我不会将其用于此目的.日常生成器几乎从不处理发送给他们的值——我想正是出于我上面给出的原因.

If I create two lists and zip them

a=[1,2,3]
b=[7,8,9]
z=zip(a,b)

Then I typecast z into two lists

l1=list(z)
l2=list(z)

Then the contents of l1 turn out to be fine [(1,7),(2,8),(3,9)], but the contents of l2 is just [].

I guess this is the general behavior of python with regards to iterables. But as a novice programmer migrating from the C family, this doesn't make sense to me. Why does it behave in such a way? And is there a way to get past this problem?

I mean, yeah in this particular example, I can just copy l1 into l2, but in general is there a way to 'reset' whatever Python uses to iterate 'z' after I iterate it once?

解决方案

There's no way to "reset" a generator. However, you can use itertools.tee to "copy" an iterator.

>>> z = zip(a, b)
>>> zip1, zip2 = itertools.tee(z)
>>> list(zip1)
[(1, 7), (2, 8), (3, 9)]
>>> list(zip2)
[(1, 7), (2, 8), (3, 9)]

This involves caching values, so it only makes sense if you're iterating through both iterables at about the same rate. (In other words, don't use it the way I have here!)

Another approach is to pass around the generator function, and call it whenever you want to iterate it.

def gen(x):
    for i in range(x):
        yield i ** 2

def make_two_lists(gen):
    return list(gen()), list(gen())

But now you have to bind the arguments to the generator function when you pass it. You can use lambda for that, but a lot of people find lambda ugly. (Not me though! YMMV.)

>>> make_two_lists(lambda: gen(10))
([0, 1, 4, 9, 16, 25, 36, 49, 64, 81], [0, 1, 4, 9, 16, 25, 36, 49, 64, 81])

I hope it goes without saying that under most circumstances, it's better just to make a list and copy it.

Also, as a more general way of explaining this behavior, consider this. The point of a generator is to produce a series of values, while maintaining some state between iterations. Now, at times, instead of simply iterating over a generator, you might want to do something like this:

z = zip(a, b)
while some_condition():
    fst = next(z, None)
    snd = next(z, None)
    do_some_things(fst, snd)
    if fst is None and snd is None:
        do_some_other_things()

Let's say this loop may or may not exhaust z. Now we have a generator in an indeterminate state! So it's important, at this point, that the behavior of a generator is restrained in a well-defined way. Although we don't know where the generator is in its output, we know that a) all subsequent accesses will produce later values in the series, and b) once it's "empty", we've gotten all the items in the series exactly once. The more ability we have to manipulate the state of z, the harder it is to reason about it, so it's best that we avoid situations that break those two promises.

Of course, as Joel Cornett points out below, it is possible to write a generator that accepts messages via the send method; and it would be possible to write a generator that could be reset using send. But note that in that case, all we can do is send a message. We can't directly manipulate the generator's state, and so all changes to the state of the generator are well-defined (by the generator itself -- assuming it was written correctly!). send is really for implementing coroutines, so I wouldn't use it for this purpose. Everyday generators almost never do anything with values sent to them -- I think for the very reasons I give above.

这篇关于如何防止迭代器耗尽?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆