一、yield關鍵字
yield的英文單詞意思是生產,在函數中但凡出現yield關鍵字,再調用函數,就不會繼續執行函數體代碼,而是會返回一個值。
def func():
print(1)
yield
print(2)
yield
g = func()
print(g)
<generator object func at 0x10ddb6b48>
生成器的本質就是迭代器,同時也並不僅僅是迭代器,不過迭代器之外的用途實在是不多,所以我們可以大聲地說:生成器提供了非常方便的自定義迭代器的途徑。並且從Python 2.5+開始,[PEP 342:通過增強生成器實現協同程序]的實現為生成器加入了更多的特性,這意味着生成器還可以完成更多的工作。這部分我們會在稍后的部分介紹。
def func():
print('from func 1')
yield 'a'
print('from func 2')
yield 'b'
g = func()
print(F"g.__iter__ == g: {g.__iter__() == g}")
res1 = g.__next__()
print(f"res1: {res1}")
res2 = next(g)
print(f"res2: {res2}")
# next(g) # StopIteration
g.__iter__ == g: True
from func 1
res1: a
from func 2
res2: b
def func():
print('from func 1')
yield 'a'
print('from func 2')
yield 'b'
g = func()
for i in g:
print(i)
print(f"list(func()): {list(func())}")
from func 1
a
from func 2
b
from func 1
from func 2
list(func()): ['a', 'b']
1.1 yield+return??
既然生成器函數也是函數,那么它可以使用return輸出返回值嗎?
親,既然你都選擇自定義一個函數作為生成器,你還return干啥?如果這是在Python2中,Python解釋器會贈送給你一個異常,但是在Python3中,他也不管你這種傻瓜行為了。
def i_wanna_return():
yield 'a'
yield 'b'
return None
yield 'c'
for i in i_wanna_return():
print(i)
a
b
1.2 迭代器套迭代器
如果我需要在生成器的迭代過程中接入另一個生成器的迭代怎么辦?寫成下面這樣好傻好天真。並且你這樣做的意圖是什么???
def sub_generator():
yield 1
yield 2
for i in range(3):
yield i
for i in sub_generator():
print(i)
1
2
0
1
2
def sub_generator():
yield 1
yield 2
yield from range(3)
for i in sub_generator():
print(i)
1
2
0
1
2
二、協同程序
協同程序(協程)一般來說是指這樣的函數:
- 彼此間有不同的局部變量、指令指針,但仍共享全局變量;
- 可以方便地掛起、恢復,並且有多個入口點和出口點;
- 多個協同程序間表現為協作運行,如A的運行過程中需要B的結果才能繼續執行。
協程的特點決定了同一時刻只能有一個協同程序正在運行(忽略多線程的情況)。得益於此,協程間可以直接傳遞對象而不需要考慮資源鎖、或是直接喚醒其他協程而不需要主動休眠,就像是內置了鎖的線程。在符合協程特點的應用場景,使用協程無疑比使用線程要更方便。
從另一方面說,協程無法並發其實也將它的應用場景限制在了一個很狹窄的范圍,這個特點使得協程更多的被拿來與常規函數進行比較,而不是與線程。當然,線程比協程復雜許多,功能也更強大,所以我建議大家牢牢地掌握線程即可,是不是聽了一臉懵逼,那么就別管他了,因為並發編程你會重新學習他。因此這一節里我也就不列舉關於協程的例子了,以下介紹的方法了解即可。
由於Python2.5+對生成器的增強實現了協程的其他特點,在這個版本中,生成器加入了如下方法:
2.1 send(value):
send是除next外另一個恢復生成器的方法。Python2.5+中,yield語句變成了yield表達式,這意味着yield現在可以有一個值,而這個值就是在生成器的send方法被調用從而恢復執行時,調用send方法的參數。
def h():
print('--start--')
first = yield 5 # 等待接收 Fighting! 值
print('1', first)
second = yield 12 # 等待接收 hahaha! 值
print('2', second)
yield 13
print('--end--')
g = h()
first = next(g) # m 獲取了yield 5 的參數值 5
# (yield 5)表達式被賦予了'Fighting!', d 獲取了yield 12 的參數值12
second = g.send('Fighting!')
third = g.send('hahaha!') # (yield 12)表達式被賦予了'hahaha!'
print(f'--over--')
print(f"first:{first}, second:{second}, third:{third}")
--start--
1 Fighting!
2 hahaha!
--over--
first:5, second:12, third:13
- 調用send傳入非None值前,生成器必須處於掛起狀態,否則將拋出異常。不過,未啟動的生成器仍可以使用None作為參數調用send。
- 如果使用next恢復生成器,yield表達式的值將是None。
2.2 close()
這個方法用於關閉生成器。對關閉的生成器后再次調用next或send將拋出StopIteration異常。
def repeater():
n = 0
while True:
n = (yield n)
r = repeater()
r.close()
print(next(r)) # StopIteration
2.3 throw(type, value=None, traceback=None)
中斷Generator是一個非常靈活的技巧,可以通過throw拋出一個GeneratorExit異常來終止Generator。Close()方法作用是一樣的,其實內部它是調用了throw(GeneratorExit)的。我們看close的源代碼:
def close(self):
try:
self.throw(GeneratorExit)
except (GeneratorExit, StopIteration):
pass
else:
raise RuntimeError("generator ignored GeneratorExit") # Other exceptions are not caught
三、自定義range()方法
def my_range(start, stop, step=1):
while start < stop:
yield start
start += 1
g = my_range(0, 3)
print(f"list(g): {list(g)}")
list(g): [0, 1, 2]
四、總結
yield:
- 提供一種自定義迭代器的方式
- yield可以暫停住函數,並提供當前的返回值
yield和return:
- 相同點:兩者都是在函數內部使用,都可以返回值,並且返回值沒有類型和個數的限制
- 不同點:return只能返回一次之;yield可以返回多次值
五、生成器表達式
- 把列表推導式的[]換成()就是生成器表達式
- 優點:省內存,一次只產生一個值在內存中
t = (i for i in range(10))
print(t)
print(f"next(t): {next(t)}")
<generator object <genexpr> at 0x1101c4888>
next(t): 0
5.1 生成器表達式和列表推導式
列表推導式相當於直接給你一筐蛋,而生成器表達式相當於給你一只老母雞。
# 生成器表達式
with open('52.txt', 'r', encoding='utf8') as f:
nums = [len(line) for line in f]
print(max(nums))
1
# 列表推導式
with open('52.txt','r',encoding='utf8') as f:
nums = (len(line) for line in f)
print(max(nums)) # ValueError: I/O operation on closed file.
六、自定義range方法(復雜版本)
def range(*args, **kwargs):
if not kwargs:
if len(args) == 1:
count = 0
while count < args[0]:
yield count
count += 1
if len(args) == 2:
start, stop = args
while start < stop:
yield start
start += 1
if len(args) == 3:
start, stop, step = args
while start < stop:
yield start
start += step
else:
step = 1
if len(args) == 1:
start = args[0]
if len(args) == 2:
start, stop = args
for k, v in kwargs.items():
if k not in ['start', 'step', 'stop']:
raise ('參數名錯誤')
if k == 'start':
start = v
elif k == 'stop':
stop = v
elif k == 'step':
step = v
while start < stop:
yield start
start += step
for i in range(3):
print(i)
print('*' * 50)
for i in range(99, 101):
print(i)
print('*' * 50)
for i in range(1, 10, 3):
print(i)
print('*' * 50)
for i in range(1, step=2, stop=5):
print(i)
print('*' * 50)
for i in range(1, 10, step=2):
print(i)