yield教學 python “yield”關鍵字有什麼作用?



15 Answers

Grokking yield捷徑

當你看到一個帶有yield語句的函數時,應用這個簡單的技巧來理解會發生什麼:

  1. 在函數的開頭插入行result = []
  2. result.append(expr)替換每個yield expr
  3. 在函數底部插入一行return result
  4. 耶 - 沒有更多的yield聲明! 閱讀並找出代碼。
  5. 比較功能與原始定義。

這個技巧可以讓你了解函數背後的邏輯,但是實際上與yield與基於列表的方法中發生的情況有很大不同。 在許多情況下,yield方法將更高效,更快。 在其他情況下,即使原始函數工作得很好,這個技巧也會讓你陷入無限循環。 請繼續閱讀以了解更多信息...

不要混淆你的Iterables,Iterators和Generators

首先, 迭代器協議 - 當你寫

for x in mylist:
    ...loop body...

Python執行以下兩個步驟:

  1. 獲取mylist的迭代器:

    調用iter(mylist) - >這將返回一個帶有next()方法的對象(或Python 3中的__next__() )。

    [這是大多數人忘記告訴你的步驟]

  2. 使用迭代器循環遍歷項目:

    繼續調用從步驟1返回的迭代器上的next()方法。將next()的返回值賦給x並執行循環體。 如果從next()引發異常StopIteration ,則意味著迭代器中沒有更多值,並且退出循環。

事實上,Python在任何時候想要循環對象的內容時執行上述兩個步驟 - 所以它可能是for循環,但它也可能是像otherlist.extend(mylist)代碼( otherlist列表是Python列表) 。

這裡mylist是一個可迭代的,因為它實現了迭代器協議。 在用戶定義的類中,您可以實現__iter__()方法以使類的實例可迭代。 此方法應返回迭代器 。 迭代器是一個帶有next()方法的對象。 可以在同一個類上實現__iter__()next() ,並使__iter__()返回self 。 這適用於簡單的情況,但是當您希望兩個迭代器同時循環遍歷同一個對象時。

所以這是迭代器協議,許多對象實現了這個協議:

  1. 內置列表,詞典,元組,集,文件。
  2. 用戶定義的實現__iter__()
  3. 發電機。

請注意, for循環不知道它正在處理什麼類型的對象 - 它只是遵循迭代器協議,並且很樂意在它調用next()獲得項目。 內置列表逐個返回它們的項目,字典逐個返回 ,文件一個接一個地返回等。然後生成器返回......那就是yield來源:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

如果你在f123()有三個return語句而不是yield語句,那麼只會執行第一個語句,並且函數將退出。 但是f123()不是普通的功能。 當f123() ,它返回yield語句中的任何值! 它返回一個生成器對象。 此外,該功能並沒有真正退出 - 它進入暫停狀態。 當for循環試圖遍歷生成器對象時,該函數在之前返回的yield之後的下一行從其掛起狀態恢復,執行下一行代碼,在本例中為yield語句,並將其返回為下一個項目。 這種情況一直發生,直到函數退出,此時生成器引發StopIteration ,循環退出。

所以生成器對像有點像適配器 - 它的一端展示了迭代器協議,通過暴露__iter__()next()方法來保持for循環的快樂。 然而,在另一端,它運行該功能足以從中獲取下一個值,並將其重新置於掛起模式。

為什麼要使用發電機?

通常,您可以編寫不使用生成器但實現相同邏輯的代碼。 一種選擇是使用我之前提到的臨時列表'技巧'。 這在所有情況下都不起作用,例如,如果你有無限循環,或者當你有一個很長的列表時,它可能會使內存的使用效率低下。 另一種方法是實現一個新的可迭代類SomethingIter ,它將狀態保存在實例成員中,並在它的next() (或Python 3中的__next__() )方法中執行下一個邏輯步驟。 根據邏輯, next()方法中的代碼可能看起來非常複雜並且容易出錯。 這裡的發電機提供了一個簡潔的解決方案

python yield用法

Python中yield關鍵字的用途是什麼? 它有什麼作用?

例如,我正在嘗試理解這段代碼1

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  

這是來電者:

result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

調用方法_get_child_candidates時會發生什麼? 列表是否返回? 單個元素? 它又被召喚了嗎? 後續通話何時停止?

1.代碼來自Jochen Schulz(jrschulz),他為度量空間創建了一個很棒的Python庫。 這是完整源代碼的鏈接: 模塊mspace




yield關鍵字簡化為兩個簡單的事實:

  1. 如果編譯器在函數內的任何位置檢測到yield關鍵字,則該函數不再通過return語句return相反 ,它會立即返回一個稱為生成器的惰性“掛起列表”對象
  2. 生成器是可迭代的。 什麼是可迭代的 ? 它類似於listsetrange或字典視圖,具有用於按特定順序訪問每個元素內置協議

簡而言之: 生成器是一個惰性的,遞增掛起的列表yield語句允許您使用函數表示法來編程生成器應逐漸吐出的列表值

generator = myYieldingFunction(...)
x = list(generator)

   generator
       v
[x[0], ..., ???]

         generator
             v
[x[0], x[1], ..., ???]

               generator
                   v
[x[0], x[1], x[2], ..., ???]

                       StopIteration exception
[x[0], x[1], x[2]]     done

list==[x[0], x[1], x[2]]

讓我們定義一個函數makeRange就像Python的range 。 調用makeRange(n)發生器:

def makeRange(n):
    # return 0,1,2,...,n-1
    i = 0
    while i < n:
        yield i
        i += 1

>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>

要強制生成器立即返回其掛起值,您可以將其傳遞給list() (就像您可以任意迭代一樣):

>>> list(makeRange(5))
[0, 1, 2, 3, 4]

比較“只返回列表”的示例

上面的例子可以被認為只是創建一個你追加並返回的列表:

# list-version                   #  # generator-version
def makeRange(n):                #  def makeRange(n):
    """return [0,1,2,...,n-1]""" #~     """return 0,1,2,...,n-1"""
    TO_RETURN = []               #>
    i = 0                        #      i = 0
    while i < n:                 #      while i < n:
        TO_RETURN += [i]         #~         yield i
        i += 1                   #          i += 1  ## indented
    return TO_RETURN             #>

>>> makeRange(5)
[0, 1, 2, 3, 4]

但是有一個主要的區別; 見最後一節。

你如何使用發電機

可迭代是列表推導的最後一部分,並且所有生成器都是可迭代的,因此它們經常被使用:

#                   _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]

為了更好地了解生成器,您可以使用itertools模塊(確保在保證時使用chain.from_iterable而不是chain )。 例如,您甚至可以使用生成器來實現無限長的惰性列表,例如itertools.count() 。 您可以實現自己的def enumerate(iterable): zip(count(), iterable) ,或者在while循環中使用yield關鍵字執行此操作。

請注意:生成器實際上可以用於更多的事情,例如實現協同程序或非確定性編程或其他優雅的東西。 但是,我在這裡提出的“懶惰列表”觀點是您將找到的最常見的用途。

在幕後

這就是“Python迭代協議”的工作原理。 也就是說, list(makeRange(5))時會發生什麼list(makeRange(5)) 。 這就是我之前描述的“懶惰的增量列表”。

>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

內置函數next()只調用對象.next()函數,它是“迭代協議”的一部分,可以在所有迭代器上找到。 您可以手動使用next()函數(以及迭代協議的其他部分)來實現花哨的東西,通常以犧牲可讀性為代價,因此盡量避免這樣做......

細節

通常情況下,大多數人不會關心以下區別,可能想在這裡停止閱讀。

在Python中, 可迭代是任何“理解for循環的概念”的對象,如列表[1,2,3]迭代器是所請求的for循環的特定實例,如[1,2,3].__iter__()生成器與任何迭代器完全相同,除了它的編寫方式(使用函數語法)。

從列表中請求迭代器時,它會創建一個新的迭代器。 但是,當您從迭代器(您很少這樣做)請求迭代器時,它只會為您提供自身的副本。

因此,萬一你沒有做到這樣的事情......

> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]

...然後記住發電機是一個迭代器 ; 也就是說,它是一次性的。 如果要重複使用它,則應再次調用myRange(...) 。 如果需要使用結果兩次,請將結果轉換為列表並將其存儲在變量x = list(myRange(5)) 。 那些絕對需要克隆生成器的人(例如,誰正在做可怕的hackish元編程)可以使用itertools.tee如果絕對必要,因為可複制的迭代器Python PEP標準提案已被推遲。




yield就像return- 它返回你告訴它的任何東西(作為一個發生器)。區別在於下次調用生成器時,執行從最後一次調用yield語句開始。與返回不同,當產生收益時不會清除堆棧幀,但是控制被轉移回調用者,因此其狀態將在下次函數時恢復。

對於代碼,該函數get_child_candidates的作用類似於迭代器,因此當您擴展列表時,它會一次向新列表添加一個元素。

list.extend調用迭代器直到它耗盡。對於您發布的代碼示例,只返回一個元組並將其附加到列表中將更加清晰。




對於那些喜歡最小工作示例的人,請冥想這個交互式Python會話:

>>> def f():
...   yield 1
...   yield 2
...   yield 3
... 
>>> g = f()
>>> for i in g:
...   print i
... 
1
2
3
>>> for i in g:
...   print i
... 
>>> # Note that this time nothing was printed



TL; DR

而不是這個:

def squares_list(n):
    the_list = []                         # Replace
    for x in range(n):
        y = x * x
        the_list.append(y)                # these
    return the_list                       # lines

做這個:

def squares_the_yield_way(n):
    for x in range(n):
        y = x * x
        yield y                           # with this one.

每當你發現自己從頭開始建立一個列表時,yield每一件都是。

這是我第一次有收益的“啊哈”時刻。

yield 是一種含糖的方式

建立一系列的東西

相同的行為:

>>> for square in squares_list(4):
...     print(square)
...
0
1
4
9
>>> for square in squares_the_yield_way(4):
...     print(square)
...
0
1
4
9

不同的行為:

收益是單程:你只能迭代一次。當函數有一個yield時,我們將其稱為生成函數。和iterator是它返回。這是揭示。我們失去了容器的便利性,但卻獲得了任意長篇系列的力量。

收益是懶惰的,它推遲了計算。當你調用它時,一個帶有yield的函數實際上根本不會執行。它返回的迭代器對象使用magic來維護函數的內部上下文。每次調用next()迭代器(這發生在for循環中)執行時,前進到下一個yield。(return提升StopIteration和結束系列。)

產量是多才多藝的。它可以做無限循環:

>>> def squares_all_of_them():
...     x = 0
...     while True:
...         yield x * x
...         x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
...     print(next(squares))
...
0
1
4
9

如果你需要多次通過並且系列不是太長,只需要打電話list()

>>> list(squares_the_yield_way(4))
[0, 1, 4, 9]

yield因為這兩個含義都適用,所以這個詞的選擇很棒

產量 - 生產或提供(如農業)

...提供系列中的下一個數據。

收益 - 放棄或放棄(如政治權力)

...放棄CPU執行直到迭代器前進。




在我描述如何使用發電機的許多重要答案中,有一種我認為尚未給出的答案。這是編程語言理論的答案:

yieldPython中的語句返回一個生成器。Python中的生成器是一個返回continuation的函數(特別是一種coroutine,但continuation代表了更常用的機制來理解正在發生的事情)。

編程語言理論的延續是一種更為基礎的計算,但它們並不經常使用,因為它們極難推理並且也很難實現。但是,延續的概念很簡單:計算的狀態還沒有完成。在此狀態下,將保存變量的當前值,尚未執行的操作等。然後在程序的某個時刻,可以調用continuation,以便程序的變量重置為該狀態,並執行保存的操作。

以這種更一般的形式,可以以兩種方式實現連續。在call/cc方式,程序的堆棧字面上保存,然後調用延續時,堆棧恢復。

在連續傳遞樣式(CPS)中,continuation只是普通函數(僅在函數是第一類的語言中),程序員明確地管理它並傳遞給子例程。在這種風格中,程序狀態由閉包(以及碰巧在其中編碼的變量)表示,而不是駐留在堆棧中某處的變量。管理控制流的函數接受繼續作為參數(在CPS的某些變體中,函數可以接受多個延續)並通過簡單地調用它們並在之後返回來調用它們來操縱控制流。延續傳遞樣式的一個非常簡單的例子如下:

def save_file(filename):
  def write_file_continuation():
    write_stuff_to_file(filename)

  check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)

在這個(非常簡單的)示例中,程序員將實際寫入文件的操作保存到一個延續中(這可能是一個非常複雜的操作,需要寫出許多細節),然後傳遞該延續(即,作為第一個 - class closure)到另一個執行更多處理的運算符,然後在必要時調用它。(我在實際的GUI編程中經常使用這種設計模式,因為它節省了我的代碼行,或者更重要的是,在GUI事件觸發後管理控制流。)

本文的其餘部分將不失一般性地將延續概念化為CPS,因為它更容易理解和閱讀。


現在讓我們談談Python中的生成器。生成器是延續的特定子類型。雖然continuation通常能夠保存計算的狀態(即程序的調用堆棧),但生成器只能通過迭代器保存迭代狀態。雖然這個定義對於某些發電機的使用情況略有誤導。例如:

def f():
  while True:
    yield 4

這顯然是一個合理的迭代,其行為很明確 - 每次生成器迭代它,它返回4(並且永遠這樣做)。但是,在考慮迭代器(即,for x in collection: do_something(x))時,它可能不是想到的典型迭代類型。這個例子說明了生成器的強大功能:如果有什麼是迭代器,生成器可以保存其迭代的狀態。

重申:Continuations可以保存程序堆棧的狀態,生成器可以保存迭代狀態。這意味著continuation比生成器更強大,但是生成器也很多,更容易。它們對於語言設計者來說更容易實現,並且程序員更容易使用它們(如果你有時間刻錄,嘗試閱讀和理解這個頁面關於continuation和call / cc)。

但是您可以輕鬆地實現(和概念化)生成器作為連續傳遞樣式的簡單特定情況:

無論什麼時候yield被調用,它都會告訴函數返回一個延續。再次調用該函數時,它從它停止的任何地方開始。因此,在偽偽代碼(即,不是偽代碼,而不是代碼)中,生成器的next方法基本如下:

class Generator():
  def __init__(self,iterable,generatorfun):
    self.next_continuation = lambda:generatorfun(iterable)

  def next(self):
    value, next_continuation = self.next_continuation()
    self.next_continuation = next_continuation
    return value

其中yield關鍵字實際上是真正的生成器函數的語法糖,基本上類似於:

def generatorfun(iterable):
  if len(iterable) == 0:
    raise StopIteration
  else:
    return (iterable[0], lambda:generatorfun(iterable[1:]))

請記住,這只是偽代碼,Python中生成器的實際實現更複雜。但是,作為了解正在發生的事情的練習,嘗試使用延續傳遞樣式來實現生成器對象而不使用yield關鍵字。




雖然很多答案都說明了為什麼要使用a yield創建生成器,但有更多的用途yield。製作協程非常容易,它可以在兩個代碼塊之間傳遞信息。我不會重複已經給出的關於使用yield創建生成器的任何精細示例。

為了幫助理解yield以下代碼中的功能,您可以用手指在任何具有代碼的代碼中跟踪循環yield。每當你的手指擊中時yield,你必須等待a next或a send進入。當a next被調用時,你會遍歷代碼,直到你點擊yield... yield評估右側的代碼並返回給調用者...然後你等待。當next被再次調用,您通過代碼進行另一次循環。但是,您會注意到在協程中,yield也可以與send... 一起使用,它會將調用者的值發送讓步函數中。如果send給出了a ,那麼yield接收發送的值,並將其從左側吐出...然後通過代碼的跟踪進行直到yield再次擊中(在結束時返回值,就像next被調用一樣)。

例如:

>>> def coroutine():
...     i = -1
...     while True:
...         i += 1
...         val = (yield i)
...         print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()



我將發布“閱讀Beazley的'Python:Essential Reference'第19頁,以便快速描述發生器”,但是很多其他人已經發布了很好的描述。

另外,請注意,yield可以在協同程序中使用它們作為它們在生成器函數中的雙重使用。雖然它與您的代碼片段的用法不同,(yield)但可以用作函數中的表達式。當調用者使用該send()方法向方法發送值時,協程將執行,直到(yield)遇到下一個語句。

生成器和協同程序是設置數據流類型應用程序的一種很酷的方法。我認為yield在函數中知道語句的其他用法是值得的。




從編程的角度來看,迭代器實現為thunks

要將並發執行的迭代器,生成器和線程池等實現為thunks(也稱為匿名函數),可以使用發送給具有調度程序的閉包對象的消息,並且調度程序將回答“消息”。

http://en.wikipedia.org/wiki/Message_passing

next ”是發送到閉包的消息,由“ iter ”調用創建。

有很多方法可以實現這個計算。我使用了變異,但通過返回當前值和下一個yielder,很容易做到沒有變異。

這是一個使用R6RS結構的演示,但語義與Python完全相同。它是相同的計算模型,只需要在Python中重寫它就需要改變語法。

Welcome to Racket v6.5.0.3.

-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
->



這是一個簡單的例子:

def isPrimeNumber(n):
    print "isPrimeNumber({}) call".format(n)
    if n==1:
        return False
    for x in range(2,n):
        if n % x == 0:
            return False
    return True

def primes (n=1):
    while(True):
        print "loop step ---------------- {}".format(n)
        if isPrimeNumber(n): yield n
        n += 1

for n in primes():
    if n> 10:break
    print "wiriting result {}".format(n)

輸出:

loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call

我不是一個Python開發人員,但它看起來我yield保持程序流的位置和下一個循環從“yield”位置開始。似乎它正在等待那個位置,就在此之前,將值返回到外部,然後下一次繼續工作。

這似乎是一個有趣而且很好的能力:D




像每個答案所暗示的那樣,yield用於創建序列生成器。它用於動態生成一些序列。例如,在網絡上逐行讀取文件時,可以使用以下yield函數:

def getNextLines():
   while con.isOpen():
       yield con.read()

您可以在代碼中使用它,如下所示:

for line in getNextLines():
    doSomeThing(line)

執行控制轉移問題

執行foryield時,執行控件將從getNextLines()傳送到循環。因此,每次調用getNextLines()時,執行都從上次暫停的位置開始。

因此簡而言之,具有以下代碼的功能

def simpleYield():
    yield "first time"
    yield "second time"
    yield "third time"
    yield "Now some useful value {}".format(12)

for i in simpleYield():
    print i

將打印

"first time"
"second time"
"third time"
"Now some useful value 12"



總之,該yield語句將您的函數轉換為一個工廠,該工廠生成一個名為a的特殊對象generator,它包裹著原始函數的主體。當generator迭代時,它執行你的函數,直到它到達下一個,yield然後暫停執行併計算傳遞給的值yield。它在每次迭代時重複此過程,直到執行路徑退出函數。例如,

def simple_generator():
    yield 'one'
    yield 'two'
    yield 'three'

for i in simple_generator():
    print i

簡單的輸出

one
two
three

電源來自使用帶有計算序列的循環的發生器,發生器每次執行循環停止以“產生”下一個計算結果,這樣它就可以動態計算列表,其好處是內存保存用於特別大的計算

假設您想要創建一個自己的range函數來生成可迭代的數字範圍,您可以這樣做,

def myRangeNaive(i):
    n = 0
    range = []
    while n < i:
        range.append(n)
        n = n + 1
    return range

並像這樣使用它;

for i in myRangeNaive(10):
    print i

但這是低效的,因為

  • 您創建一個只使用一次的數組(這會浪費內存)
  • 這段代碼實際上循環遍歷該數組兩次! :(

幸運的是,Guido和他的團隊足夠慷慨地開發發電機,所以我們可以做到這一點;

def myRangeSmart(i):
    n = 0
    while n < i:
       yield n
       n = n + 1
    return

for i in myRangeSmart(10):
    print i

現在,在每次迭代時,調用的生成器上next()的函數執行函數,直到它達到'yield'語句,在該語句中它停止並“產生”該值或到達函數的末尾。在這種情況下,在第一次調用時,next()執行yield語句並生成'n',在下一次調用時它將執行increment語句,跳回'while',計算它,如果為true,它將停止並且再次屈服'n',它將繼續這樣,直到while條件返回false並且生成器跳轉到函數的末尾。




(我的下面的答案僅從使用Python生成器的角度講,而不是生成器機制底層實現,這涉及堆棧和堆操作的一些技巧。)

當在python函數中yield使用when 而不是a return時,該函數變成了一個特殊的函數generator function。該函數將返回一個generator類型的對象。yield關鍵字是一個標誌,通知蟒蛇編譯器將特殊對待這樣的功能。一旦從其返回某個值,正常函數將終止。但是在編譯器的幫助下,生成器函數可以被認為是可恢復的。也就是說,將恢復執行上下文,並且執行將從上次運行繼續。直到你顯式調用return,這將引發StopIteration異常(也是迭代器協議的一部分),或者到達函數的末尾。我發現了很多關於引用的generator,但是這onefunctional programming perspective最可消化的。

(現在我想談談背後的基本原理generator,並iterator基於我自己的理解。我希望這可以幫助你掌握迭代器和生成器的基本動機。這樣的概念也出現在其他語言中,比如C#。)

據我所知,當我們想要處理大量數據時,我們通常首先將數據存儲在某處,然後逐個處理。但這種直觀的方法存在問題。如果數據量很大,那麼事先將它們作為一個整體存儲起來是很昂貴的。因此,不是data直接存儲自身,為什麼不metadata間接存儲某種,即the logic how the data is computed

有兩種方法來包裝這樣的元數據。

  1. OO方法,我們包裝元數據as a class。這就是所謂的iterator實現迭代器協議(即__next__(),和__iter__()方法)的人。這也是常見的迭代器設計模式
  2. 功能方法,我們包裝元數據as a function。這就是所謂的generator function。但在引擎蓋下,返回的generator object仍然是IS-A迭代器,因為它還實現了迭代器協議。

無論哪種方式,都會創建一個迭代器,即一些可以為您提供所需數據的對象。OO方法可能有點複雜。無論如何,使用哪一個取決於你。




yield關鍵字簡單地收集返回結果。想想yield就好return +=




另一個TL; DR

列表上的迭代器next()返回列表的下一個元素

迭代器生成器next()將動態計算下一個元素(執行代碼)

您可以看到yield / generator作為一種從外部手動運行控制流的方法(如繼續循環一步),通過調用next,無論流程如何復雜。

注意:生成器不是正常功能。它記住了先前的狀態,如局部變量(堆棧)。有關詳細說明,請參閱其他答案或文章。生成器只能迭代一次。你可以沒有yield,但它不會那麼好,所以它可以被認為是'非常好'的語言糖。




Related