yield是python的一個關鍵字,剛接觸python的時候對這個關鍵字只知其一;不知其二,掌握以後才發現這關鍵字有大用,本文將對yield的使用方法好好梳理一番。python
在python中,生成器是一種可迭代對象,但可迭代對象不必定是生成器。
例如,list就是一個可迭代對象shell
>>> a = list(range(3)) >>> for i in a: print(i) 0 1 2 3
可是一個list對象全部的值都是放在內存中的,若是數據量很是大的話,內存就有可能不夠用;這種狀況下,就能夠生成器,例如,python能夠用「()」構建生成器對象:編程
>>> b = (x for x in range(3)) >>> for i in b: print(i) 0 1 2 >>> for i in b: print(i) >>>
生成器能夠迭代的,而且數據實時生成,不會所有保存在內存中;值得注意的是,生成器只能讀取一次,從上面的運行結果能夠看到,第二次for循環輸出的結果爲空。ide
在實際編程中,若是一個函數須要產生一段序列化的數據,最簡單的方法是將全部結果都放在一個list裏返回,若是數據量很大的話,應該考慮用生成器來改寫直接返回列表的函數(Effective Python, Item 16).函數
>>> def get_generator(): for i in range(3): print('gen ', i) yield i >>> c = get_generator() >>> c = get_generator() >>> for i in c: print(i) gen 0 0 gen 1 1 gen 2 2
由上面的代碼能夠看出,當調用get_generator函數時,並不會執行函數內部的代碼,而是返回了一個迭代器對象,在用for循環進行迭代的時候,函數中的代碼纔會被執行。
除了使用for循環得到生成器返回的值,還可使用next和senddebug
>>> c = get_generator() >>> print(next(c)) gen 0 0 >>> print(next(c)) gen 1 1 >>> print(next(c)) gen 2 2 >>> print(next(c)) Traceback (most recent call last): File "<pyshell#59>", line 1, in <module> print(next(c)) StopIteration
>>> c = get_generator() >>> c.send(None) gen 0 0 >>> c.send(None) gen 1 1 >>> c.send(None) gen 2 2 >>> c.send(None) Traceback (most recent call last): File "<pyshell#66>", line 1, in <module> c.send(None) StopIteration
生成器的結果讀取完後,會產生一個StopIteration的異常日誌
yield一個常見的使用場景是經過yield來實現協程,已下面這個生產者消費者模型爲例:code
def consumer(): r = 'yield' while True: print('[CONSUMER] r is %s...' % r) #當下邊語句執行時,先執行yield r,而後consumer暫停,此時賦值運算還未進行 #等到producer調用send()時,send()的參數做爲yield r表達式的值賦給等號左邊 n = yield r #yield表達式能夠接收send()發出的參數 if not n: return # 這裏會raise一個StopIteration print('[CONSUMER] Consuming %s...' % n) r = '200 OK' def produce(c): c.send(None) n = 0 while n < 5: n = n + 1 print('[PRODUCER] Producing %s...' % n) r = c.send(n) #調用consumer生成器 print('[PRODUCER] Consumer return: %s' % r) c.send(None) c.close() c = consumer() produce(c)
[CONSUMER] r is yield... [PRODUCER] Producing 1... [CONSUMER] Consuming 1... [CONSUMER] r is 200 OK... [PRODUCER] Consumer return: 200 OK [PRODUCER] Producing 2... [CONSUMER] Consuming 2... [CONSUMER] r is 200 OK... [PRODUCER] Consumer return: 200 OK [PRODUCER] Producing 3... [CONSUMER] Consuming 3... [CONSUMER] r is 200 OK... [PRODUCER] Consumer return: 200 OK [PRODUCER] Producing 4... [CONSUMER] Consuming 4... [CONSUMER] r is 200 OK... [PRODUCER] Consumer return: 200 OK [PRODUCER] Producing 5... [CONSUMER] Consuming 5... [CONSUMER] r is 200 OK... [PRODUCER] Consumer return: 200 OK Traceback (most recent call last): File ".\foobar.py", line 51, in <module> produce(c) File ".\foobar.py", line 47, in produce c.send(None) StopIteration
在上面的例子中能夠看到,yield表達式與send配合,能夠起到交換數據的效果,協程
n = yield r r = c.send(n)
另一個比較有意思的使用場景是在contextmanager中,以下:對象
import logging import contextlib def foobar(): logging.debug('Some debug data') logging.error('Some error data') logging.debug('More debug data') @contextlib.contextmanager def debug_logging(level): logger = logging.getLogger() old_level = logger.getEffectiveLevel() logger.setLevel(level) try: yield #這裏表示with塊中的語句 finally: logger.setLevel(old_level) with debug_logging(logging.DEBUG): print('inside context') foobar() print('outside context') foobar()
inside context DEBUG:root:Some debug data ERROR:root:Some error data DEBUG:root:More debug data outside context ERROR:root:Some error data
在上面的代碼中,經過使用上下文管理器(contextmanager)來臨時提高了日誌的等級,yield表示with塊中的語句;
yield表達式能夠建立生成器,應該考慮使用生成器來改寫直接返回list的函數;
因爲生成器只能讀取一次,所以使用for循環遍歷的時候要格外注意;生成器讀取完後繼續讀的話會raise一個StopIteration的異常,實際編程中可使用這個異常來做爲讀取終止的判斷依據;
yield一個常見的使用場景是實現協程;經過與send函數的配合,能夠起到交換數據的效果;
yield還能夠在contextmanager修飾的函數中表示with塊中的語句;