python中的yield使用詳解

yield是python的一個關鍵字,剛接觸python的時候對這個關鍵字只知其一;不知其二,掌握以後才發現這關鍵字有大用,本文將對yield的使用方法好好梳理一番。python

1 使用yield建立生成器

在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的異常日誌

2 coroutines中使用

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)

3 contextmanager中使用

另一個比較有意思的使用場景是在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塊中的語句;

相關文章
相關標籤/搜索