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




15 Answers

要了解yield作用,您必須了解發電機是什麼。 在發電機出現之前。

Iterables

創建列表時,您可以逐個閱讀其項目。 逐個讀取它的項稱為迭代:

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

mylist是一個可迭代的 。 當您使用列表推導時,您創建一個列表,因此是一個可迭代的:

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...    print(i)
0
1
4

您可以使用“ for... in... ”的所有內容都是可迭代的; listsstrings ,文件......

這些迭代很方便,因為您可以根據需要讀取它們,但是您將所有值存儲在內存中,當您擁有大量值時,這並不總是您想要的。

發電機

生成器是迭代器,是一種只能迭代一次的迭代器。 生成器不會將所有值存儲在內存中, 它們會動態生成值

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

除了你使用()而不是[]之外,它是一樣的。 但是,你不能 for i in mygenerator執行for i in mygenerator因為生成器只能使用一次:它們計算0,然後忘記它併計算1,然後逐個計算4。

產量

yield是一個像return一樣使用的關鍵字,除了函數將返回一個生成器。

>>> def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

這是一個無用的例子,但是當你知道你的函數將返回一組你只需要閱讀一次的大量值時它會很方便。

要掌握yield ,您必須明白, 當您調用函數時,您在函數體中編寫的代碼不會運行。 該函數只返回生成器對象,這有點棘手:-)

然後,每次使用生成器時,都會運行代碼。

現在困難的部分:

第一次調用從函數創建的生成器對象時,它將從頭開始運行函數中的代碼,直到達到yield ,然後它將返回循環的第一個值。 然後,每個其他調用將再次運行您在函數中寫入的循環,並返回下一個值,直到沒有值返回。

一旦函數運行,該生成器被認為是空的,但不再達到yield 。 這可能是因為循環已經結束,或者因為你不再滿足"if/else"

你的代碼解釋了

發電機:

# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there is no more than two values: the left and the right children

呼叫者:

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidates list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

此代碼包含幾個智能部分:

  • 循環在列表上迭代,但是循環迭代時列表會擴展:-)這是一個簡單的方法來遍歷所有這些嵌套數據,即使它有點危險,因為你最終可以得到一個無限循環。 在這種情況下, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))耗盡了生成器的所有值,但while不斷創建新的生成器對象,這些對象將生成與之前的值不同的值,因為它不應用於相同的值節點。

  • extend()方法是一個列表對象方法,它需要一個iterable並將其值添加到列表中。

通常我們將列表傳遞給它:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

但是在你的代碼中它得到了一個生成器,這很好,因為:

  1. 您不需要兩次讀取值。
  2. 您可能有很多孩子,並且您不希望它們都存儲在內存中。

它的工作原理是因為Python不關心方法的參數是否是列表。 Python期望iterables所以它將適用於字符串,列表,元組和生成器! 這叫做鴨子打字,這也是Python如此酷的原因之一。 但這是另一個故事,另一個問題......

你可以在這裡停下來,或者閱讀一下看看發電機的高級用途:

控制發電機的耗盡

>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

注意:對於Python 3,使用print(corner_street_atm.__next__())print(next(corner_street_atm))

它可以用於控制對資源的訪問等各種事情。

Itertools,你最好的朋友

itertools模塊包含操作iterables的特殊函數。 曾經希望復制發電機嗎? 鏈兩個發電機? 使用單行分組嵌套列表中的值? Map / Zip而不創建另一個列表?

然後只需import itertools

一個例子? 讓我們來看看四匹馬比賽的可能到達順序:

>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
 (1, 2, 4, 3),
 (1, 3, 2, 4),
 (1, 3, 4, 2),
 (1, 4, 2, 3),
 (1, 4, 3, 2),
 (2, 1, 3, 4),
 (2, 1, 4, 3),
 (2, 3, 1, 4),
 (2, 3, 4, 1),
 (2, 4, 1, 3),
 (2, 4, 3, 1),
 (3, 1, 2, 4),
 (3, 1, 4, 2),
 (3, 2, 1, 4),
 (3, 2, 4, 1),
 (3, 4, 1, 2),
 (3, 4, 2, 1),
 (4, 1, 2, 3),
 (4, 1, 3, 2),
 (4, 2, 1, 3),
 (4, 2, 3, 1),
 (4, 3, 1, 2),
 (4, 3, 2, 1)]

理解迭代的內在機制

迭代是一個暗示迭代(實現__iter__()方法)和迭代器(實現__next__()方法)的過程。 Iterables是可以從中獲取迭代器的任何對象。 迭代器是允許您迭代迭代的對象。

在這篇文章中有關於for循環如何工作的更多信息

yield用途 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




想一想:

對於具有next()方法的對象,迭代器只是一個奇特的聲音術語。 因此,屈服函數最終會像這樣:

原始版本:

def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i

這基本上是Python解釋器對上面代碼的作用:

class it:
    def __init__(self):
        # Start at -1 so that we get 0 when we add 1 below.
        self.count = -1

    # The __iter__ method will be called once by the 'for' loop.
    # The rest of the magic happens on the object returned by this method.
    # In this case it is the object itself.
    def __iter__(self):
        return self

    # The next method will be called repeatedly by the 'for' loop
    # until it raises StopIteration.
    def next(self):
        self.count += 1
        if self.count < 4:
            return self.count
        else:
            # A StopIteration exception is raised
            # to signal that the iterator is done.
            # This is caught implicitly by the 'for' loop.
            raise StopIteration

def some_func():
    return it()

for i in some_func():
    print i

為了更深入地了解幕後發生的事情,可以將for循環重寫為:

iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass

這更有意義還是只是讓你感到困惑? :)

我應該注意到,這僅僅是為了說明目的而過於簡單化。 :)




yield關鍵字在Python中有什麼作用?

回答大綱/摘要

  • 帶有yield的函數在被調用時返回一個Generator
  • 生成器是迭代器,因為它們實現了迭代器協議 ,因此您可以迭代它們。
  • 還可以生成器發送信息 ,使其在概念上成為協程
  • 在Python 3中,您可以在兩個方向上從一個生成器委派給另一個生成器,其中yield from
  • (附錄批評了幾個答案,包括最重要的答案,並討論了在發電機中使用return 。)

發電機:

yield在函數定義中只是合法的,並且函數定義中包含yield使它返回一個生成器。

生成器的想法來自其他語言(見腳註1),具有不同的實現。 在Python的Generators中,代碼的執行在yield的時候被frozen 。 當調用生成器時(下面討論方法),執行將恢復,然後在下一次生成時凍結。

yield提供了一種實現迭代器協議的簡單方法, 該協議由以下兩種方法定義: __iter__next (Python 2)或__next__ (Python 3)。 這兩種方法都使對象成為迭代器,您可以使用collections模塊中的Iterator Abstract Base Class進行類型檢查。

>>> def func():
...     yield 'I am'
...     yield 'a generator!'
... 
>>> type(func)                 # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen)                  # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__')   # that's an iterable
True
>>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
True                           # implements the iterator protocol.

生成器類型是迭代器的子類型:

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

如有必要,我們可以像這樣打字檢查:

>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

Iterator一個特性是,一旦耗盡 ,您就無法重用或重置它:

>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

如果您想再次使用其功能,則必須製作另一個(參見腳註2):

>>> list(func())
['I am', 'a generator!']

可以以編程方式生成數據,例如:

def func(an_iterable):
    for item in an_iterable:
        yield item

上面的簡單生成器也等同於下面 - 從Python 3.3開始(在Python 2中不可用),你可以使用yield from

def func(an_iterable):
    yield from an_iterable

但是, yield from也允許委託給子發電機,這將在下面關於與子程序的協同授權的部分中解釋。

協同程序:

yield表示允許將數據發送到生成器的表達式(參見腳註3)

下面是一個示例,請注意received變量,該變量將指向發送到生成器的數據:

def bank_account(deposited, interest_rate):
    while True:
        calculated_interest = interest_rate * deposited 
        received = yield calculated_interest
        if received:
            deposited += received


>>> my_account = bank_account(1000, .05)

首先,我們必須使用內置函數排隊生成器, next 。 它將調用適當的next__next__方法,具體取決於您使用的Python版本:

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

現在我們可以將數據發送到生成器。 ( 發送None與調用next相同 。):

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

合作代表團參與Sub-Coroutine的yield from

現在,回想一下,Python 3中提供了yield from 。這允許我們將協同程序委託給子協會:

def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()

現在我們可以將功能委託給子生成器,它可以像生成器一樣使用,如上所述:

>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

您可以在PEP 380中閱讀有關yield from的精確語義的更多信息

其他方法:關閉並拋出

close方法在函數執行被凍結時引發GeneratorExit 。 這也將由__del__調用,因此您可以將任何清理代碼放在處理GeneratorExit

>>> my_account.close()

您還可以拋出一個異常,該異常可以在生成器中處理或傳播回用戶:

>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

結論

我相信我已經涵蓋了以下問題的所有方面:

yield關鍵字在Python中有什麼作用?

事實證明, yield很高。 我相信我可以為此添加更全面的例子。 如果您想要更多或有一些建設性的批評,請通過下面的評論告訴我。

附錄:

批評最高/已接受的答案**

  • 關於什麼使迭代成為混亂,僅使用列表作為示例。 請參閱上面的參考資料,但總結一下:iterable有一個__iter__方法返回迭代器迭代器提供了一個.next (Python 2或.__next__ (Python 3)方法,它被for循環隱式調用,直到它引發StopIteration ,一旦它啟動,它將繼續這樣做。
  • 然後它使用生成器表達式來描述生成器是什麼。 由於生成器只是創建迭代器的一種方便方法,因此它只會混淆事情,我們仍然沒有達到yield部分。
  • 控制生成器耗盡時,他調用.next方法,而在next他應該使用內置函數。 這將是一個適當的間接層,因為他的代碼在Python 3中不起作用。
  • Itertools? 這與yield完全沒有關係。
  • 在Python 3中沒有討論yield提供的方法以及新功能。 最高/接受的答案是一個非常不完整的答案。

對答案的批判表明在生成器表達或理解中的yield

語法當前允許列表理解中的任何表達。

expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist

由於屈服是一種表達,因此在一些理解或生成器表達中使用它已被一些人吹捧為有趣 - 儘管沒有引用特別好的用例。

CPython核心開發人員正在討論棄用其配額 。 這是郵件列表中的相關帖子:

2017年1月30日19:05,Brett Cannon寫道:

在Sun,2017年1月29日16:39 Craig Rodrigues寫道:

無論採用哪種方法,我都行。 恕我直言,將它們放在Python 3中的方式並不好。

我的投票是它是一個SyntaxError,因為你沒有得到你對語法的期望。

我同意這對我們來說是一個明智的地方,因為任何依賴當前行為的代碼實在太聰明而無法維護。

在達到目標方面,我們可能會想:

  • 3.7中的SyntaxWarning或DeprecationWarning
  • 2.7.x中的Py3k警告
  • 3.8中的SyntaxError

乾杯,尼克。

- Nick Coghlan | ncoghlan at gmail.com | 澳大利亞布里斯班

此外,還有一個突出的問題(10544)似乎指向了這個永遠不是一個好主意的方向(PyPy,一個用Python編寫的Python實現,已經提出了語法警告。)

最後,直到CPython的開發人員告訴我們: 不要將yield放在生成器表達式或理解中。

生成器中的return語句

Python 2中

在生成器函數中,不允許return語句包含expression_list 。 在該上下文中,裸return表示生成器已完成並將導致StopIteration被引發。

An expression_list基本上是用逗號分隔的任意數量的表達式 - 實質上,在Python 2中,您可以使用停止生成器return,但不能返回值。

Python 3中

在生成器函數中,該return語句指示生成器已完成並將導致StopIteration生成。返回值(如果有)用作構造的參數StopIteration並成為StopIteration.value屬性。

腳註

  1. 提議中引用了CLU,Sather和Icon語言,以便將生成器的概念引入Python。一般的想法是函數可以維持內部狀態並根據用戶的要求產生中間數據點。這承諾在性能上優於其他方法,包括Python線程,在某些系統上甚至不可用。

  2. 這意味著,例如,xrange對象(range在Python 3中)不是Iterators,即使它們是可迭代的,因為它們可以被重用。與列表一樣,它們的__iter__方法返回迭代器對象。

  3. yield最初是作為語句引入的,這意味著它只能出現在代碼塊中一行的開頭。現在yield創建一個yield表達式。https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt這種變化提出以允許用戶將數據發送到發電機,就像一個會接受它。要發送數據,必須能夠將其分配給某些內容,為此,語句將無效。




還有一件事需要提及:一個實際上不必終止收益的函數。我編寫了這樣的代碼:

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

然後我可以在其他代碼中使用它,如下所示:

for f in fib():
    if some_condition: break
    coolfuncs(f);

它確實有助於簡化一些問題,並使一些事情更容易使用。




產量為您提供發電機。

def get_odd_numbers(i):
    return range(1, i, 2)
def yield_odd_numbers(i):
    for x in range(1, i, 2):
       yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5

如您所見,在第一種情況下,foo會立即將整個列表保存在內存中。對於包含5個元素的列表來說,這不是什麼大問題,但是如果你想要一個500萬的列表呢?這不僅是一個巨大的內存消耗者,而且在調用函數時也需要花費大量時間來構建。在第二種情況下,bar只給你一個發電機。生成器是可迭代的 - 這意味著您可以在for循環等中使用它,但每個值只能被訪問一次。所有值也不會同時存儲在內存中;生成器對象“記住”上次調用它時循環的位置 - 這樣,如果你使用一個可迭代(比如說)計數到500億,那麼你不需要數到500億全部立刻存儲500億個數字。再次,這是一個非常人為的例子,如果你真的想要數到500億,你可能會使用itertools。 :)

這是生成器最簡單的用例。正如你所說,它可以用來編寫有效的排列,使用yield來通過調用堆棧推送,而不是使用某種堆棧變量。生成器也可以用於專門的樹遍歷,以及其他各種方式。




它正在返回一台發電機。我對Python並不是特別熟悉,但我相信它與C#的迭代器塊相同,如果你熟悉它們的話。

關鍵的想法是編譯器/解釋器/無論做什麼都有一些技巧,因此就調用者而言,他們可以繼續調用next()並且它將保持返回值 - 就好像生成器方法被暫停一樣。現在顯然你不能真正“暫停”一個方法,所以編譯器會建立一個狀態機,讓你記住你當前的位置以及局部變量等。這比自己編寫迭代器容易得多。




這是一個簡單語言的例子。我將提供高級人類概念與低級Python概念之間的對應關係。

我想對一系列數字進行操作,但我不想因為創建該序列而煩惱我自己,我只想專注於我想要做的操作。所以,我做了以下事情:

  • 我打電話給你,告訴你我想要一個以特定方式產生的數字序列,我告訴你算法是什麼。
    該步驟對應於def生成器函數,即包含a的函數yield
  • 過了一會兒,我告訴你,“好的,準備告訴我數字的順序”。
    此步驟對應於調用返回生成器對象的生成器函數。請注意,你還沒有告訴我任何數字; 你抓住你的紙和鉛筆。
  • 我問你,“告訴我下一個號碼”,你告訴我第一個號碼; 之後,你等我問你下一個號碼。這是你的工作,要記住你在哪裡,你已經說過的數字,以及下一個數字是什麼。我不關心細節。
    此步驟對應於調用.next()生成器對象。
  • ...重複上一步,直到......
  • 最終,你可能會走到盡頭。你沒告訴我一個號碼; 你只是喊道,“抓住你的馬!我已經完成了!沒有更多的數字!”
    此步驟對應於生成器對象結束其作業,並引發StopIteration異常生成器函數不需要引發異常。當函數結束或發出時,它會自動引發return

這就是生成器所做的事情(包含a的函數yield); 它開始執行,只要它執行一次就暫停yield,當被要求輸入一個.next()值時,它會從最後一次繼續執行。它完全符合Python的迭代器協議設計,它描述瞭如何順序請求值。

迭代器協議最著名的用戶是forPython中的命令。所以,每當你做一個:

for item in sequence:

如果sequence是如上所述的列表,字符串,字典或生成器對象並不重要; 結果是一樣的:你逐個讀取序列中的項目。

請注意,def包含yield關鍵字的函數不是創建生成器的唯一方法; 這只是創建一個最簡單的方法。

有關更準確的信息,請閱讀Python文檔中的迭代器類型yield語句generators




還有另一種yield用途和含義(自Python 3.3起):

yield from <expr>

PEP 380 - 委託給子發電機的語法

提出了一種語法,用於生成器將其部分操作委託給另一個生成器。這允許將包含'yield'的一段代碼分解出來並放在另一個生成器中。此外,允許子生成器返回一個值,該值可供委派生成器使用。

當一個生成器重新生成另一個生成器生成的值時,新語法也為優化提供了一些機會。

此外,this將介紹(自Python 3.5):

async def new_coroutine(data):
   ...
   await blocking_action()

避免協程與常規生成器混淆(今天yield兩者都使用)。




下面是一些如何實際實現生成器的Python示例,就好像Python沒有為它們提供語法糖:

作為Python生成器:

from itertools import islice

def fib_gen():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))

使用詞法閉包而不是生成器

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen2():
    #funky scope due to python2.x workaround
    #for python 3.x use nonlocal
    def _():
        _.a, _.b = _.b, _.a + _.b
        return _.a
    _.a, _.b = 0, 1
    return _

assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

使用對象閉包而不是生成器(因為ClosuresAndObjectsAreEquivalent

class fib_gen3:
    def __init__(self):
        self.a, self.b = 1, 1

    def __call__(self):
        r = self.a
        self.a, self.b = self.b, self.a + self.b
        return r

assert [1,1,2,3,5] == ftake(fib_gen3(), 5)



所有偉大的答案,但新手有點困難。

我假設你已經學會了這個return陳述。

作為類比,return並且yield是雙胞胎。return意味著'回歸和停止',而'產量'意味著'回歸,但繼續'

  1. 嘗試獲取num_list return
def num_list(n):
    for i in range(n):
        return i

運行:

In [5]: num_list(3)
Out[5]: 0

看,你只得到一個數字而不是它們的列表。return永遠不會讓你高興,只需實施一次並退出。

  1. 來了 yield

替換returnyield

In [10]: def num_list(n):
    ...:     for i in range(n):
    ...:         yield i
    ...:

In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>

In [12]: list(num_list(3))
Out[12]: [0, 1, 2]

現在,你贏了所有的數字。

比較return哪一次運行和停止,yield運行您計劃的時間。你可以理解returnreturn one of them,和yield作為return all of them。這叫做iterable

  1. 我們可以yield用另一個步驟重寫語句return
In [15]: def num_list(n):
    ...:     result = []
    ...:     for i in range(n):
    ...:         result.append(i)
    ...:     return result

In [16]: num_list(3)
Out[16]: [0, 1, 2]

這是關鍵的核心yield

列表return輸出和對象yield輸出之間的區別是:

您將始終從列表對像中獲取[0,1,2],但只能從“對象yield輸出”中檢索一次。因此,它有一個新的名稱generator對象,如圖所示Out[11]: <generator object num_list at 0x10327c990>

總之,作為一個隱喻它的隱喻:

  • return並且yield是雙胞胎
  • list並且generator是雙胞胎



這是一個什麼yield做的心理形象。

我喜歡將一個線程視為具有堆棧(即使它沒有以這種方式實現)。

當調用普通函數時,它將其局部變量放在堆棧上,進行一些計算,然後清除堆棧並返回。其局部變量的值再也看不到了。

使用yield函數,當它的代碼開始運行時(即在調用函數之後,返回一個生成器對象,next()然後調用其方法),它同樣將其局部變量放入堆棧併計算一段時間。但是,當它遇到yield語句時,在清除其部分堆棧並返回之前,它會獲取其局部變量的快照並將它們存儲在生成器對像中。它還會在其代碼(即特定yield語句)中寫下它當前所處的位置。

所以它是發電機懸掛的一種凍結功能。

next()隨後被調用時,它檢索功能的物品入堆棧,重新蓬勃生機。該功能繼續從它停止的地方進行計算,不知道它剛剛在冷庫中度過了一個永恆的事實。

比較以下示例:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

當我們調用第二個函數時,它的行為與第一個函數的行為非常不同。該yield聲明可能是無法訪問的,但如果它的存在的任何地方,它改變了我們正在處理什麼用的性質。

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

調用yielderFunction()不會運行其代碼,但會使代碼生成一個生成器。(也許最好用yielder可讀性的前綴命名這些東西。)

>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

gi_codegi_frame字段是凍結狀態的存儲位置。通過探索它們dir(..),我們可以確認我們的上述心理模型是可信的。




收益率是一個對象

return函數中的A 將返回單個值。

如果您希望函數返回一組大量值,請使用yield

更重要的yield是,是一個障礙

就像CUDA語言中的屏障一樣,它不會在控製完成之前進行轉移。

也就是說,它將從頭開始運行代碼,直到它命中yield。然後,它將返回循環的第一個值。

然後,每隔一個調用將再次運行您在函數中編寫的循環,返回下一個值,直到沒有任何值返回。




yield就像一個函數的返回元素。不同之處在於,yield元素將函數轉換為生成器。在某些東西“屈服”之前,生成器的行為就像一個函數。發電機停止,直到下一次調用,並從它開始的完全相同的點繼續。您可以通過調用將所有“已產生”值的序列合二為一list(generator())




許多人使用return而不是yield,但在某些情況下yield可以更有效,更容易使用。

這是一個yield絕對最適合的例子:

返回(在功能中)

import random

def return_dates():
    dates = [] # With 'return' you need to create a list then return it
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        dates.append(date)
    return dates

產量(功能)

def yield_dates():
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        yield date # 'yield' makes a generator automatically which works
                   # in a similar way. This is much more efficient.

調用函數

dates_list = return_dates()
print(dates_list)
for i in dates_list:
    print(i)

dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
    print(i)

這兩個函數都做同樣的事情,但yield使用三行而不是五行,並且有一個較少的變量需要擔心。

這是代碼的結果:

正如您所看到的,兩個函數都做同樣的事情。唯一的區別是return_dates()給出一個列表並yield_dates()給出一個生成器。

一個現實生活中的例子就像是逐行讀取文件或者只是想製作一個生成器。




這是一個簡單yield的方法,用於計算斐波納契系列,解釋如下:

def fib(limit=50):
    a, b = 0, 1
    for i in range(limit):
       yield b
       a, b = b, a+b

當你在REPL中輸入它然後嘗試調用它時,你會得到一個神秘的結果:

>>> fib()
<generator object fib at 0x7fa38394e3b8>

這是因為yield您希望創建生成器,即根據需要生成值的對象,向Python發出信號。

那麼,你如何生成這些值?這可以通過使用內置函數直接完成,也可以next通過將其提供給消耗值的構造來間接完成。

使用內置next()函數,直接調用.next/ __next__,強制生成器生成一個值:

>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5

間接地,如果你提供fib一個for循環,一個list初始化器,一個tuple初始化器或任何其他需要生成/生成值的對象的東西,你將“消耗”生成器,直到它不再生成值(並返回) :

results = []
for i in fib(30):       # consumes fib
    results.append(i) 
# can also be accomplished with
results = list(fib(30)) # consumes fib

同樣,使用tuple初始化程序:

>>> tuple(fib(5))       # consumes fib
(1, 1, 2, 3, 5)

生成器與函數的不同之處在於它是惰性的。它通過維護本地狀態並允許您隨時恢復來實現此目的。

當您第一次fib通過調用它來調用時:

f = fib()

Python編譯函數,遇到yield關鍵字並簡單地返回一個生成器對象。似乎不是很有幫助。

然後,當您請求它直接或間接生成第一個值時,它會執行它找到的所有語句,直到遇到a yield,然後它會返回您提供給的值yield並暫停。有關更好地演示此示例的示例,讓我們使用一些print調用(print "text"在Python 2上替換為if):

def yielder(value):
    """ This is an infinite generator. Only use next on it """ 
    while 1:
        print("I'm going to generate the value for you")
        print("Then I'll pause for a while")
        yield value
        print("Let's go through it again.")

現在,輸入REPL:

>>> gen = yielder("Hello, yield!")

你有一個生成器對象現在正在等待命令讓它生成一個值。使用next並查看打印內容:

>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

未加引號的結果是印刷的。引用的結果是從中返回的結果yieldnext現在再打電話:

>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

發電機記得它暫停yield value並從那裡恢復。打印下一條消息yield,再次執行搜索暫停的語句(由於while循環)。




Related