假如你需要?jiǎng)?chuàng)建一個(gè)很大很大的列表,但你只需要列表中的某幾個(gè)元素舍悯,這樣就會(huì)造成浪費(fèi)拱层,因?yàn)榇蟛糠值脑貙?duì)你而言都沒(méi)有用骇两,而這些沒(méi)有用的元素卻依舊占用著內(nèi)存。
如果類表中的元素可以通過(guò)某種算法推算出來(lái)拳喻,我們可以在循環(huán)的過(guò)程中不斷推算出來(lái)后續(xù)元素哭当,這樣就不需要?jiǎng)?chuàng)建完整的list了。在Python中就提供了一個(gè)叫生成器的東西可以解決這個(gè)問(wèn)題:generator冗澈。
我們前面學(xué)了列表生成式钦勘,生成器只需要將列表生成式中的[ ]改成( )就可以了。
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1057af0a0>
如果你想打印 g 中的每一個(gè)元素可以通過(guò)next()函數(shù)獲取generator的下一個(gè)元素:
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
File "<pyshell#13>", line 1, in <module>
next(g)
StopIteration
```
如果下一個(gè)元素不存在亚亲,就會(huì)報(bào)錯(cuò)彻采。而且next()函數(shù)太麻煩了腐缤,你可以使用for循環(huán):
```
>>> g = (x * x for x in range(10))
>>> for x in g:
print(x)
0
1
4
9
16
25
36
49
64
81
```
> 提醒:如果上面這一步你沒(méi)有重新創(chuàng)建 g ,而是直接執(zhí)行for循環(huán)的話肛响,你會(huì)發(fā)現(xiàn)什么也打印不出來(lái)岭粤,因?yàn)間enerator中的指針已經(jīng)指到了generator的末尾了。
怎么樣特笋!生成器中的元素都是在你需要的時(shí)候才通過(guò)算法生成的剃浇,所以不會(huì)浪費(fèi)內(nèi)存空間。
但是猎物,有時(shí)候這個(gè)算法會(huì)特別復(fù)雜虎囚,用類似于列表生成式的for循環(huán)無(wú)法實(shí)現(xiàn),這時(shí)候你可以使用函數(shù)來(lái)實(shí)現(xiàn)這個(gè)算法蔫磨。
比如淘讥,斐波拉契數(shù)列,前兩個(gè)數(shù)已知质帅,后面的數(shù)都是它前面兩個(gè)數(shù)的和:
1适揉,1,2煤惩,3嫉嘀,5,8魄揉,13剪侮,21,34……
你無(wú)法用列表生成式的方式寫(xiě)出來(lái)洛退。但你可以使用函數(shù):
```
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(n)
a, b = b, a+b
n = n+1
return 'done'
```
事實(shí)上瓣俯,這就是一個(gè)推導(dǎo)斐波那契數(shù)列的算法,跟generator很像兵怯,可以從第一個(gè)元素開(kāi)始彩匕,依次推導(dǎo)出后面的元素。
如果你想讓函數(shù)變成一個(gè)generator的話很簡(jiǎn)單媒区,只需要將上面的print(n)變成 yield b 就可以了驼仪。
```
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a+b
n = n+1
return 'done'
```
這樣fib()函數(shù)返回的就是一個(gè)generator對(duì)象了:
```
>>> f = fib(7)
>>> f
<generator object fib at 0x1057af0a0>
```
這時(shí)比較難以理解的一點(diǎn)是,此時(shí)generator的執(zhí)行流程跟函數(shù)的執(zhí)行流程不一樣袜漩。函數(shù)是順序執(zhí)行的绪爸,遇到return或者函數(shù)末尾時(shí)會(huì)返回,而通過(guò)函數(shù)來(lái)生成generator的時(shí)候宙攻,執(zhí)行流程就不一樣了奠货。generator會(huì)在遇到next()的時(shí)候執(zhí)行,遇到y(tǒng)ield的時(shí)候返回座掘,再次執(zhí)行時(shí)递惋,從上一次返回的yield語(yǔ)句繼續(xù)執(zhí)行柔滔。
舉個(gè)例子:
```
def test():
print('first')
yield 1
print('second')
yield 2
print('third')
yield 3
```
首先得到generator對(duì)象,然后通過(guò)next()函數(shù)不斷地得到下一個(gè)返回值:
```
>>> t = test()
>>> next(t)
first
1
>>> next(t)
second
2
>>> next(t)
third
3
>>> next(t)
Traceback (most recent call last):
File "<pyshell#50>", line 1, in <module>
next(t)
StopIteration
```
這里的test不是普通的函數(shù)萍虽,而是一個(gè)generator廊遍。它在執(zhí)行過(guò)程中,遇到y(tǒng)ield就中斷贩挣,并返回yield后面跟著的值喉前,下次又從中斷的地方繼續(xù)執(zhí)行。
再回到fib的例子中王财,我們第一次調(diào)用next()的時(shí)候卵迂,fib函數(shù)開(kāi)始執(zhí)行,它在yield的地方返回b的值绒净,也就是1见咒,然后它就中斷了,等待著你第二次調(diào)用next()挂疆,你第二次調(diào)用next()的時(shí)候改览,它從上次中斷的地方開(kāi)始執(zhí)行,a = b缤言,b = a + b宝当,n = n + 1,緊接著又遇到了yield了胆萧,又返回b庆揩,并中斷。然后你第三次調(diào)用next()跌穗,它又從上次中斷的地方開(kāi)始執(zhí)行……
當(dāng)然我們一般情況下不會(huì)直接調(diào)用next()订晌,我們通常使用for循環(huán):
```
>>> for n in fib(6):
print(n)
1
1
2
3
5
8
```
不過(guò)需要注意的一點(diǎn)是,使用for循環(huán)你是得不到最終函數(shù)return的結(jié)果的蚌吸。如果你想得到return值锈拨,你需要捕獲StopIteration錯(cuò)誤,返回值包含在StopIteration的value中:
```
>>> g = fib(6)
>>> while true:
try:
x = next(g)
print('g:', x)
except StopIteration as e:
print('函數(shù)返回值:', e.value)
break;
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
函數(shù)返回值: done
```
捕獲錯(cuò)誤會(huì)在后面講羹唠。