yield全面總結

本文首發於知乎
yield生成器在python中使用普遍,更是python中協程的實現原理,有必要深刻掌握。python

本文分爲以下幾個部分編程

  • 簡單yield的使用
  • yield空
  • yield from
  • send與yield賦值
  • return yield

簡單yield的使用

下面是一個最簡單的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處中止,等待下一次nextnext次數超出限制就會拋出異常函數

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空

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

經過下面一個例子來展現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與yield賦值

先講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,用於初始化生成器

  1. 整個程序運行細節很是重要,詳細拆解以下
  • 第一步a.send(None),是執行第一次循環,運行到yield i。注意:沒有運行r = yield賦值這一步,這也是第一次只能傳入None的緣由
  • 第二步a.send(1),先r = yield賦值,將send進去的1賦值給了r;而後print(r)即第一個打印出來的1;以後進入第二次循環,運行到yield i沒有賦值,把yield的結果1打印出來,即第二個1
  • 最後一次a.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 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編程

專欄目錄:目錄

版本說明:軟件及包版本說明

相關文章
相關標籤/搜索