使用“无限”迭代关闭循环时会发生什么?

时间:2017-03-12 02:29:50

标签: python

我编写了以下python函数:

import numpy

def primes_iterable():
    """Iterable giving the primes"""
    # The lowest primes
    primes = [2,3,5]
    for p in primes:
        yield p
    for n in potential_primes():
        m = int(numpy.sqrt(n))
        check = True
        for p in primes:
            if p > m:
                break
            if n%p == 0:
                check = False
        if check:
            primes.append(n)
            yield n

def potential_primes():
    """Iterable starting at 7 and giving back the non-multiples of 2,3,5"""
    yield 7
    n = 7
    gaps = [4,2,4,2,4,6,2,6]
    while 1:
        for g in gaps:
            n += g
            yield n

如您所见,这两个函数都没有return语句。假设我要写这样的东西:

for p in primes_iterable():
    if p > 1000:
        break
    print p

达到break语句时,内存级别会发生什么?如果我理解正确,调用primes_iterable()会使函数启动,直到下一个yield,然后暂停直到再次需要它。当达到break语句时,函数实例是否关闭,或者它是否继续存在于背景中,完全没用?

3 个答案:

答案 0 :(得分:4)

您的函数primes_iterable是一个生成器函数。当你调用它时,没有任何事情立即发生(除了它返回一个生成器对象)。只有在调用next时,它才会运行到下一个yield

调用生成器函数时,会得到一个可迭代的生成器对象。如果您在for循环中执行此操作,则循环将在生成器运行时保留对生成器对象的引用。如果您break离开循环,则释放该引用并且可以对生成器对象进行垃圾回收。

但是当清理生成器对象时,生成器函数中运行的代码会发生什么?它被暂停的GeneratorStop引发的yield异常中断了。如果需要,您可以让您的生成器功能捕获此异常,但除了清理资源和退出之外,您无法执行任何有用的操作。这通常使用try / finally对,而不是except语句。

以下是一些演示行为的示例代码:

def gen():
    print("starting")
    try:
        while 1:
            yield "foo"
    except GeneratorExit:
        print("caught GeneratorExit")
        raise
    finally:
        print("cleaning up")

这是一个示例运行:

>>> for i, s in enumerate(gen()):
    print(s)
    if i >= 3:
        break

starting
foo
foo
foo
foo
caught GeneratorExit
cleaning up

答案 1 :(得分:2)

当您从break循环for时,没有任何引用留给生成器,因此它最终将被垃圾收集...
为了清楚起见,调用primes_iterable()创建了一个生成器。在生成器上调用next()将控制权传递给生成器,它将一直运行到yieldfor隐式调用next()每个循环。

考虑一下:

prime = primes_iterable()
print(next(prime))    # 2
for p in prime:
    if p > 1000:
        break
    print(p)          # 3, 5, 7, ...

现在你仍然有一个名为prime的生成器的引用,所以你总能得到下一个素数:

print(next(prime))    # 1013

答案 2 :(得分:1)

primes_iterable()会返回迭代器。这是一个在您调用next时会吐出新值的对象。这就是幕后for循环的作用。试试这个:

it = primes_iterable()
print(next(it))
print(next(it))

需要注意的是,it并非永远不会在幕后运行,它只是运行得足以在您提出要求时吐出新值。它会保留其数据,以便随时可以重新开始运行,但您无法访问该数据。

现在,在您的代码中,

for p in primes_iterable():

如上所述primes_iterable已被调用并且返回了迭代器,尽管在这种情况下迭代器没有名称(即它没有绑定到变量)。对于循环的每个步骤,p将被赋值给迭代器的next

    if p > 1000:
        break

现在我们分解并且for循环停止在迭代器上运行next。 Nothing不再引用迭代器(您可以通过调用dir()来检查它,它会显示全局命名空间中定义的所有内容)。

因此,经过一段时间后,Python释放了迭代器占用的内存。这称为垃圾收集。它也会发生什么,例如,您在解释器中键入[1,2,3]但不将其绑定到变量名称。它被创建但随后被有效删除以释放空间,因为它毫无意义。

您可以(并且应该)在此处阅读有关迭代器的更多信息:

https://docs.python.org/3/tutorial/classes.html#iterators