我编写了以下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
语句时,函数实例是否关闭,或者它是否继续存在于背景中,完全没用?
答案 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()
将控制权传递给生成器,它将一直运行到yield
。 for
隐式调用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]
但不将其绑定到变量名称。它被创建但随后被有效删除以释放空间,因为它毫无意义。
您可以(并且应该)在此处阅读有关迭代器的更多信息: