本文首發於知乎
yield生成器在python中使用普遍,更是python中協程的實現原理,有必要深刻掌握。python
本文分爲以下幾個部分編程
下面是一個最簡單的yield使用的例子ruby
def myfun(total):
for i in range(total):
yield i
複製代碼
定義了myfun
函數,調用時好比myfun(4)
獲得的是一個生成器,生成器有3種調用方式bash
1.用next調用app
>>> m = myfun(4)
>>> next(m)
0
>>> next(m)
1
>>> next(m)
2
>>> next(m)
3
>>> next(m)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
複製代碼
每次調用一次next
,函數都會執行到yield
處中止,等待下一次next
,next
次數超出限制就會拋出異常函數
2.循環調用網站
for i in myfun(4):
print(i)
複製代碼
運行結果爲ui
0
1
2
3
複製代碼
生成器是可迭代對象,能夠用循環調用。循環調用就是最大限度地調用next
,並返回每次next
運行結果spa
這就是yield的最基本使用,下面來應用一下code
python內置模塊itertools中實現了一些小的生成器,咱們這裏拿count
舉例子,要實現下面這樣的效果
>>> from itertools import count
>>> c = count(start = 5, step = 2)
>>> next(c)
5
>>> next(c)
7
>>> next(c)
9
>>> next(c)
11
......
複製代碼
實現以下
def count(start, step = 1):
n = 0
while True:
yield start + n
n += step
複製代碼
3.循環中調用next
下面循環中調用next
,用StopIteration
捕獲異常並退出循環
>>> m = myfun(4)
>>> while True:
... try:
... print(next(m))
... except StopIteration:
... break
...
0
1
2
3
複製代碼
yield空至關於一箇中斷器,循環運行到這裏會中斷,用於輔助其餘程序的執行。也能夠理解成返回值是None
,咱們來看下面這個例子
>>> def myfun(total):
... for i in range(total):
... print(i + 1)
... yield
...
>>> a = myfun(3)
>>> next(a)
1
>>> next(a)
2
>>> next(a)
3
>>> next(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> a = myfun(3)
>>> for i in a:
... print(i)
...
1
None
2
None
3
None
複製代碼
經過下面一個例子來展現yield from
的用法
def myfun(total):
for i in range(total):
yield i
yield from ['a', 'b', 'c']
複製代碼
用next
調用結果以下
>>> m = myfun(3)
>>> next(m)
0
>>> next(m)
1
>>> next(m)
2
>>> next(m)
'a'
>>> next(m)
'b'
>>> next(m)
'c'
>>> next(m)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
複製代碼
上面的函數至關於
def myfun(total):
for i in range(total):
yield i
for i in ['a', 'b', 'c']:
yield i
複製代碼
下面咱們也作一個小練習,實現itertools模塊中的cycle
,效果以下
>>> from itertools import cycle
>>> a = cycle('abc')
>>> next(a)
'a'
>>> next(a)
'b'
>>> next(a)
'c'
>>> next(a)
'a'
>>> next(a)
'b'
>>> next(a)
'c'
>>> next(a)
'a'
複製代碼
實現以下
def cycle(p):
yield from p
yield from cycle(p)
複製代碼
先講send
,首先明確一點,next
至關於send(None)
,仍是看下面最簡單的例子
>>> def myfun(total):
... for i in range(total):
... yield i
...
>>> a = myfun(3)
>>> a.send(None)
0
>>> a.send(None)
1
>>> a.send(None)
2
>>> a.send(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
複製代碼
若是send()
參數不是None
呢?send()
表示向這個生成器中傳入東西,有傳入就得有變量接着,因而引出了yield
賦值,來看下面一個例子
def myfun(total):
for i in range(total):
r = yield i
print(r)
複製代碼
運行以下
>>> a = myfun(3)
>>> a.send(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't send non-None value to a just-started generator
>>> a.send(None)
0
>>> a.send(1)
1
1
>>> a.send(1)
1
2
>>> a.send(1)
1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
複製代碼
上面運行結果顯示
1.第一次send
參數必須是None
,用於初始化生成器
a.send(None)
,是執行第一次循環,運行到yield i
。注意:沒有運行r = yield
賦值這一步,這也是第一次只能傳入None
的緣由a.send(1)
,先r = yield
賦值,將send
進去的1賦值給了r
;而後print(r)
即第一個打印出來的1;以後進入第二次循環,運行到yield i
沒有賦值,把yield
的結果1打印出來,即第二個1a.send(1)
先對r
賦值,再print(r)
;而後就退出了循環,沒有什麼能夠yield
的了,因而拋出異常有了send
這樣的機制,咱們就能夠實現函數之間的來回切換執行,這是協程的基礎。
廖雪峯老師網站上用這一特性完成了一個相似生產者消費者模式的例子,讀者能夠看看根據上面的知識能不能看懂這個例子
def consumer():
r = ''
while True:
n = yield r
if not n:
return
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)
print('[PRODUCER] Consumer return: %s' % r)
c.close()
c = consumer()
produce(c)
複製代碼
運行結果以下
[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK
複製代碼
上面例子看起來複雜高大上,可是若是從實現上述功能上來看,其實是走了彎路,下面的代碼能夠實現和上面同樣的功能,讀者能夠細細品味
def consumer(n):
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
return '200 OK'
def produce():
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = consumer(n)
print('[PRODUCER] Consumer return: %s' % r)
produce()
複製代碼
有時會看到return yield
的用法,其實return
只是起到終止函數的做用,先看下面這個函數
def myfun(total):
yield from range(total)
複製代碼
>>> a = myfun(4)
>>> a
<generator object myfun at 0x000001B61CCB9CA8>
>>> for i in a:
... print(i)
...
0
1
2
3
複製代碼
這樣a
就是個生成器。若是用return
也同樣
# 不加括號不合規定
>>> def myfun(total):
... return yield from range(total)
File "<stdin>", line 2
return yield from range(total)
^
SyntaxError: invalid syntax
>>> def myfun(total):
... return (yield from range(total))
...
>>> a = myfun(4)
>>> for i in a:
... print(i)
...
0
1
2
3
複製代碼
不過下面兩個不同
def myfun1(total):
return (yield from range(total))
yield 1 # 這個1在return後面,不會再執行
def myfun2(total):
yield from range(total)
yield 1
複製代碼
看下面運行結果
>>> a = myfun1(3)
>>> for i in a:
... print(i)
...
0
1
2
>>> a = myfun2(3)
>>> for i in a:
... print(i)
...
0
1
2
1
複製代碼
專欄主頁:python編程
專欄目錄:目錄
版本說明:軟件及包版本說明