簡體   English   中英

遞歸python生成器:為什么需要對yield進行迭代?

[英]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繼續執行遞歸調用,而不是產生整個生成器對象。

returnyield之間的區別在於,前者只返回一個值。 后者的意思是“將值包裝在生成器中,然后返回生成器”。

因此,在所有情況下,函數perms_generator()返回一個生成器。

表達yield perms_generator()再次將包的結果perms_generator()中的發電機,給你一個發電機的發電機。 那意味着函數將返回不同的東西; 有時,它將是一個簡單的生成器,有時是嵌套的生成器。 對於您的代碼使用者,這將非常令人困惑。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM