[英]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.