[英]Recursive python generators: why does the yield need to be iterated over?
测试一个人对递归的理解的一个很好的练习是编写一个函数,该函数生成字符串的所有排列:
def get_perms(to_go, so_far=''):
if not to_go:
return [so_far]
else:
ret = []
for i in range(len(to_go)):
ret += get_perms(to_go[:i] + to_go[i+1:], so_far + to_go[i])
return ret
这段代码很好,但是从生成器的角度来看,我们可以通过使用生成器来显着提高效率:
def perms_generator(to_go, so_far=''):
if not to_go:
yield so_far
else:
for i in range(len(to_go)):
for perm in perms_generator(to_go[:i] + to_go[i+1:], so_far + to_go[i]):
yield perm
(注意:最后一个for
循环也可以用python 3.3中的yield from
代替)
我的问题:为什么我们需要迭代每个递归调用的结果? 我知道yield
返回一个生成器,但是从yield so_far
语句yield so_far
,好像我们正在获取字符串,而不是需要迭代的内容。 相反,似乎我们可以取代
for perm in perms_generator(to_go[:i] + to_go[i+1:], so_far + to_go[i]):
yield perm
同
yield perms_generator(to_go[:i] + to_go[i+1:], so_far + to_go[i])
谢谢。 如果标题不清楚,请告诉我。 我感觉这个问题的内容与此SO问题有关 。
请记住,任何使用yield
函数都不会将这些值返回给调用者。 而是返回一个生成器对象 ,并暂停代码本身,直到您遍历该生成器为止。 每次遇到yield
时,代码都会再次暂停:
>>> def pausing_generator():
... print 'Top of the generator'
... yield 1
... print 'In the middle'
... yield 2
... print 'At the end'
...
>>> gen = pausing_generator()
>>> gen
<generator object pausing_generator at 0x1081e0d70>
>>> next(gen)
Top of the generator
1
>>> next(gen)
In the middle
2
>>> next(gen)
At the end
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
调用pausing_generator()
函数将返回一个generator object
。 仅迭代(在此使用next()
函数)运行该函数中的实际代码,但是每次遇到yield
都会暂停执行。
您的perms_generator
函数将返回一个此类生成器对象,而递归调用仍将返回一个生成器对象。 您可以yield
整个生成器对象,但是随后您将生成一个生成器,该生成器将生成一个生成器,依此类推,直到到达最内层的生成器为止。
您可以使用print
语句将其可视化:
>>> def countdown(i):
... if not i:
... return
... yield i
... recursive_result = countdown(i - 1)
... print i, recursive_result
... for recursive_elem in recursive_result:
... yield recursive_elem
...
>>> for i in countdown(5):
... print i
...
5
5 <generator object countdown at 0x1081e0e10>
4
4 <generator object countdown at 0x1081e0e60>
3
3 <generator object countdown at 0x1081e0eb0>
2
2 <generator object countdown at 0x1081e0f00>
1
1 <generator object countdown at 0x1081e0f50>
在这里,递归调用返回了一个新的生成器对象; 如果您希望由生成器生成元素 ,则唯一的选择是将其循环并向下传递元素,而不是生成器对象本身。
在Python 3中,您可以使用yield from
委托给嵌套的生成器,包括递归调用:
def perms_generator(to_go, so_far=''):
if not to_go:
yield so_far
else:
for i, elem in enumerate(to_go):
yield from perms_generator(to_go[:i] + to_go[i+1:], so_far + elem)
当遇到迭代yield from
继续执行递归调用,而不是产生整个生成器对象。
return
和yield
之间的区别在于,前者只返回一个值。 后者的意思是“将值包装在生成器中,然后返回生成器”。
因此,在所有情况下,函数perms_generator()
返回一个生成器。
表达yield perms_generator()
再次将包的结果perms_generator()
中的发电机,给你一个发电机的发电机。 那意味着函数将返回不同的东西; 有时,它将是一个简单的生成器,有时是嵌套的生成器。 对于您的代码使用者,这将非常令人困惑。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.