2.4 生成器
通過列表生成式,我們可以直接創(chuàng)建一個(gè)列表甜攀。但是秋泄,受到內(nèi)存限制,列表容量肯定是有限的规阀。而且恒序,創(chuàng)建一個(gè)包含100萬個(gè)元素的列表,不僅占用很大的存儲(chǔ)空間谁撼,如果我們僅僅需要訪問前面幾個(gè)元素歧胁,那后面絕大多數(shù)元素占用的空間都白白浪費(fèi)了。
所以,如果列表元素可以按照某種算法推算出來与帆,那我們是否可以在循環(huán)的過程中不斷推算出后續(xù)的元素呢了赌?這樣就不必創(chuàng)建完整的list,從而節(jié)省大量的空間玄糟。在Python中勿她,這種一邊循環(huán)一邊計(jì)算的機(jī)制,稱為生成器:generator阵翎。
要?jiǎng)?chuàng)建一個(gè)generator逢并,有很多種方法。第一種方法很簡單郭卫,只要把一個(gè)列表生成式的
[]
改成()
砍聊,就創(chuàng)建了一個(gè)generator:
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>
創(chuàng)建L
和g
的區(qū)別僅在于最外層的[]
和()
,L是一個(gè)list贰军,而g是一個(gè)generator玻蝌。
我們可以直接打印出list的每一個(gè)元素,但我們怎么打印出generator的每一個(gè)元素呢词疼?
如果要一個(gè)一個(gè)打印出來俯树,可以通過next()函數(shù)獲得generator的下一個(gè)返回值:
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
我們講過,generator保存的是算法贰盗,每次調(diào)用next(g)
许饿,就計(jì)算出g的下一個(gè)元素的值,直到計(jì)算到最后一個(gè)元素舵盈,沒有更多的元素時(shí)陋率,拋出StopIteration的錯(cuò)誤。
當(dāng)然秽晚,上面這種不斷調(diào)用next(g)實(shí)在是太變態(tài)了瓦糟,正確的方法是使用for循環(huán),因?yàn)間enerator也是可迭代對象:
>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)
...
0
1
4
9
16
所以爆惧,我們創(chuàng)建了一個(gè)generator后狸页,基本上永遠(yuǎn)不會(huì)調(diào)用next()
锨能,而是通過for循環(huán)來迭代它扯再,并且不需要關(guān)心StopIteration
的錯(cuò)誤。
generator非常強(qiáng)大址遇。如果推算的算法比較復(fù)雜熄阻,用類似列表生成式的for循環(huán)無法實(shí)現(xiàn)的時(shí)候,還可以用函數(shù)來實(shí)現(xiàn)倔约。
比如秃殉,著名的斐波拉契數(shù)列(Fibonacci),除第一個(gè)和第二個(gè)數(shù)外,任意一個(gè)數(shù)都可由前兩個(gè)數(shù)相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波拉契數(shù)列用列表生成式寫不出來钾军,但是鳄袍,用函數(shù)把它打印出來卻很容易:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'
仔細(xì)觀察,可以看出吏恭,fib函數(shù)實(shí)際上是定義了斐波拉契數(shù)列的推算規(guī)則拗小,可以從第一個(gè)元素開始,推算出后續(xù)任意的元素樱哼,這種邏輯其實(shí)非常類似generator哀九。
也就是說,上面的函數(shù)和generator僅一步之遙搅幅。要把fib
函數(shù)變成generator阅束,只需要把print(b)
改為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'
這就是定義generator的另一種方法。如果一個(gè)函數(shù)定義中包含yield
關(guān)鍵字茄唐,那么這個(gè)函數(shù)就不再是一個(gè)普通函數(shù)息裸,而是一個(gè)generator:
>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>
這里,最難理解的就是generator和函數(shù)的執(zhí)行流程不一樣沪编。函數(shù)是順序執(zhí)行界牡,遇到return語句或者最后一行函數(shù)語句就返回。而變成generator的函數(shù)漾抬,在每次調(diào)用next()的時(shí)候執(zhí)行宿亡,遇到y(tǒng)ield語句返回,再次執(zhí)行時(shí)從上次返回的yield語句處繼續(xù)執(zhí)行纳令。
附:實(shí)現(xiàn)楊輝三角代碼
def triangles():
T=[1]
while True:
yield T
T=( ( T+[0] )[i] + ( [0]+T )[i] for i in range(len(T+[0])) )
for i in T:
print(i)
2.5 迭代器
我們已經(jīng)知道挽荠,可以直接作用于for
循環(huán)的數(shù)據(jù)類型有以下幾種:
一類是集合數(shù)據(jù)類型,如list
平绩、tuple
圈匆、dict
、set
捏雌、str
等跃赚;
一類是generator
,包括生成器和帶yield
的generator function
性湿。
這些可以直接作用于for
循環(huán)的對象統(tǒng)稱為可迭代對象:Iterable
纬傲。
可以使用isinstance()
判斷一個(gè)對象是否是Iterable
對象:
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False
而生成器不但可以作用于for
循環(huán),還可以被next()
函數(shù)不斷調(diào)用并返回下一個(gè)值肤频,直到最后拋出StopIteration錯(cuò)誤表示無法繼續(xù)返回下一個(gè)值了叹括。
可以被next()
函數(shù)調(diào)用并不斷返回下一個(gè)值的對象稱為迭代器:Iterator。
可以使用isinstance()
判斷一個(gè)對象是否是Iterator
對象:
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False
生成器都是Iterator
對象宵荒,但list
汁雷、dict
净嘀、str
雖然是Iterable
,卻不是Iterator
侠讯。
把list
挖藏、dict
、str
等Iterable
變成Iterator
可以使用iter()
函數(shù):
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
小結(jié)
凡是可作用于
for
循環(huán)的對象都是Iterable
類型厢漩;
凡是可作用于next()
函數(shù)的對象都是Iterator
類型熬苍,它們表示一個(gè)惰性計(jì)算的序列;
集合數(shù)據(jù)類型如list
袁翁、dict
柴底、str
等是Iterable
但不是Iterator
,不過可以通過iter()
函數(shù)獲得一個(gè)Iterator
對象粱胜。
Python的for
循環(huán)本質(zhì)上就是通過不斷調(diào)用next()
函數(shù)實(shí)現(xiàn)的柄驻,例如:
for x in [1, 2, 3, 4, 5]:
pass
實(shí)際上完全等價(jià)于:
# 首先獲得Iterator對象:
it = iter([1, 2, 3, 4, 5])
# 循環(huán):
while True:
try:
# 獲得下一個(gè)值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循環(huán)
break