「 yield」關鍵字有什麼做用?

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

例如,我試圖理解這段代碼1python

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)編寫的,Jochen Schulz是一個很好的用於度量空間的Python庫。 這是完整源代碼的連接: Module mspace 設計模式


#1樓

如下是一些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)

使用對象閉包而不是生成器 (由於ClosuresAndObjectsAreEquivalentui

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)

#2樓

產量能夠爲您提供發電機。 this

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能夠將內容推入調用堆棧,而不是使用某種堆棧變量。 生成器還能夠用於特殊的樹遍歷以及全部其餘方式。


#3樓

對於那些偏心簡單工做示例的人,請在此交互式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

#4樓

我本打算髮布「閱讀Beazley的「 Python:基本參考」的第19頁,以快速瞭解生成器」,可是已經有許多其餘人發佈了不錯的描述。

另外,請注意,協程能夠將yield用做生成函數的雙重用途。 儘管(yield)與代碼段用法不一樣,但它能夠用做函數中的表達式。 當調用者使用send()方法向該方法發送值時,協程將一直執行,直到遇到下一個(yield)語句爲止。

生成器和協程是設置數據流類型應用程序的一種很酷的方法。 我認爲值得了解函數中yield語句的其餘用法。


#5樓

在描述如何使用生成器的許多很棒的答案中,我尚未給出一種答案。 這是編程語言理論的答案:

Python中的yield語句返回一個生成器。 (and specifically a type of coroutine, but continuations represent the more general mechanism to understand what is going on). Python中的生成器是一個返回的函數(特別是協程類型,可是延續表明了一種更通用的機制來了解正在發生的事情)。

編程語言理論中的連續性是一種更爲基礎的計算,可是因爲它們很難推理並且也很難實現,所以並不常用。 可是,關於延續是什麼的想法很簡單:只是還沒有完成的計算狀態。 在此狀態下,將保存變量的當前值,還沒有執行的操做等。 而後,在稍後的某個時刻,能夠在程序中調用繼續,以便將程序的變量重置爲該狀態,並執行保存的操做。

以這種更通常的形式進行的延續能夠兩種方式實現。 以call/cc方式,該程序的堆棧其實是保存的,而後在調用延續時,該堆棧得以恢復。

在延續傳遞樣式(CPS)中,延續只是普通的函數(僅在函數爲第一類的語言中),程序員明確地對其進行管理並傳遞給子例程。 以這種方式,程序狀態由閉包(以及剛好在其中編碼的變量)表示,而不是駐留在堆棧中某個位置的變量。 管理控制流的函數接受連續做爲參數(在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)

在這個(很是簡單的)示例中,程序員保存了將文件實際寫入連續的操做(該操做多是很是複雜的操做,須要寫出許多細節),而後傳遞該連續(例如,首先類關閉)到另外一個進行更多處理的運算符,而後在必要時調用它。 (我在實際的GUI編程中常用這種設計模式,這是由於它節省了個人代碼行,或更重要的是,在GUI事件觸發後管理了控制流。)

在不失通常性的前提下,本文的其他部分將連續性概念化爲CPS,由於它很容易理解和閱讀。


如今讓咱們談談Python中的生成器。 生成器是延續的特定子類型。 延續一般可以保存計算狀態 (即程序的調用堆棧),而生成器只能保存迭代上的迭代狀態 。 雖然,對於發電機的某些用例,此定義有些誤導。 例如:

def f():
  while True:
    yield 4

顯然,這是一個合理的迭代器,其行爲已獲得很好的定義-每次生成器對其進行迭代時,它都會返回4(並永遠這樣作)。 可是在考慮迭代器時(例如, for x in collection: do_something(x) ),可能不會想到原型的可迭代類型。 此示例說明了生成器的功能:若是有什麼是迭代器,生成器能夠保存其迭代狀態。

重申一下:連續能夠保存程序堆棧的狀態,而生成器能夠保存迭代的狀態。 這意味着延續比生成器強大得多,可是生成器也很是簡單。 它們對於語言設計者來講更容易實現,對程序員來講也更容易使用(若是您有時間要燃燒,請嘗試閱讀並理解有關延續和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關鍵字。

相關文章
相關標籤/搜索