Python中yield關鍵字(stackoverflow高票回答)

原文地址:what is python keyword yield used for?

1.引入

首先,以一個例子開頭,如何解釋下面的代碼:

def node._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 = list(), [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被調用時發生了什麼?返回了一個list?還是返回了一個列表的元素?它被重複調用了?接下來的調用什麼時候結束?

在瞭解yield之前,我們需要了解generator,在瞭解generator之前,又必須瞭解iterables。

2.Iterables——可迭代

當你創建一個列表的時候,你可以一個一個地讀取他的元素。一個個讀取元素的過程就叫做迭代。
例如:

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

mylist就ishi可迭代的。當你使用一個列表解析器創建列表的時候,你創建的列表就是可迭代的。

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

對於一個對象,如果你能對它使用for … in …表達式,那麼它就是可迭代的,例如:lists,string,files等等等。

這些可迭代的的東東使用起來特別方便,你可以任意地讀取它們。但是,它們中的值都也是存儲在內存中的,也就意味着當你有很多值需要存儲的時候,你就會遇到麻煩(內存不夠)。

3.Generators-生成器

生成器也是迭代器的一種,但是,你僅僅可以迭代使用它們一次。因爲它們不會將迭代到的所有數據存儲在內存中,這些值是動態生成的,一直在變化。
例如;

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

除了你使用()而不是[]之外和迭代器沒有其他區別。但是你不可以這樣——for i in mygenerator使用它第二次,因爲generator只可以被使用一次;先計算0,然後忘記它,然後計算1,然後再忘記它,再計算…一個一個直到所有元素被迭代玩。

4.Yield

Yield是個類似return的關鍵字,不過它返回一個generator。

>>> 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關鍵字,你必須知道當你調用函數的時候,寫在函數體中的代碼並不會運行。函數只返回generator對象,這裏有點難懂。

然後,每次使用generator的時候,都會去運行一次函數體createGenerator()裏面的代碼。

現在我們討論最難的部分:

第一次調用我們函數創建的generator的時候,會從頭運行你函數中的代碼直到遇見yield關鍵詞,然後它會返回循環的第一個值。接下來的每次調用會再次執行你函數中的循環,並且返回下一個值,直到沒有值返回爲止。
在沒有執行到yield關鍵詞時,generator都是空值。或者是循環沒結束,再者就是你沒有滿足if/else。

5.最上面代碼的解釋

此處,你創建了node對象的一個返回generator的方法:

def node._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
  # 函數執行到此處,那麼generator就是空的
  # there is no more than two values: the left and the right children
  # 無非就兩種值:左右字節點

Caller:# 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

這段代碼的做法十分聰明:

循環在不斷擴展的list上迭代。這是一種簡單的方式遍歷嵌套的數據,即使會有一點風險——那就是可能會死循環。在此例中,candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))窮盡了generator的所有值,同時也在不斷地創建新的generator對象,這些generator對象會依賴上一個節點生成不同的值,因爲它不會依賴同一個節點。

extend()方法是list對象的方法,它的參數是一個iterable·對象,並且會將它的值添加進list中。

通常,我們會傳遞一個list對象給它。

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

但是在你的代碼中,你傳給它的是一個generator,這樣做也是OK的,因爲:

你不需要重複讀取它的值;
其次是你可以有非常多的字節點,然而你不必將它們存進內存。

上面的代碼能夠工作的原因是python不必管函數的參數是否是一個list或者其他的什麼東東。Python要的是iterables,所以參數可以是strings,lists,tuples,generators等等!這叫做動態類型(duck typing)這也是爲什麼python這麼酷的原因。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章