10-Python之路---迭代器和生成器
前言
假如現在有一個列表L = [1,2,3,4,5],如果需要獲取列表裡的內容,有幾種方法?
第一種:
L[0]
第二種:
for i in L:
print(i)
如果用索引取值,我們可以取到任意位置的值,前提是你得知道這個值在什麼位置。
如果用for迴圈來取值,我們把每個值都取到,不需要關心每個值的位置,因為只能按照順序來取值。
但是,有沒有人想過為啥可以使用for迴圈取值?
Python中的for迴圈
想搞清楚for迴圈,還是得從程式碼的角度看:
for i in [1,2,3,4,5]:
print(i)
上面這段程式碼沒啥問題,但是我們換一種情況,迴圈一組數字12345,
for i in 12345:
print(i)
# 結果:
Traceback (most recent call last):
File "D:/Mark/學員問題.py", line 4, in <module>
for i in 12345:
TypeError: 'int' object is not iterable
報錯了!莫慌,有報錯提示TypeError: 'int' object is not iterable
,說int型別不是iterable,老馬百度翻譯了一下
也就是說int型別不是可迭代的!
迭代
什麼是迭代
"可迭代"這個概念,我們可以從報錯上分析,之所以12345不可以for迴圈,是因為它不可迭代,反過來意思就是,如果"可迭代",那麼就可以被for迴圈!
我們知道,字串,列表,元組,字典,集合都可以被for迴圈,說明它們是可迭代的。
from collections import Iterable l = [1, 2, 3, 4] t = (1, 2, 3, 4) d = {1: 2, 3: 4} s = {1, 2, 3, 4} print(isinstance(l, Iterable)) print(isinstance(t, Iterable)) print(isinstance(d, Iterable)) print(isinstance(s, Iterable))
列印結果都是True,那麼迭代指的就是可以將資料一個挨著一個的取出來。
可迭代協議
能被for迴圈的就是可迭代的,但是for怎麼知道誰是可迭代的呢?
假設我們有一個數據型別,希望這個型別裡的東西可以被for迴圈,那麼就必須滿足for迴圈的要求,而這個要求就是"協議"
可以滿足被迭代的要求就叫做可迭代協議,可迭代協議的定義非常簡單,就是內部實現了__ iter__方法。
print(dir([1,2]))
print(dir((2,3)))
print(dir({1:2}))
print(dir({1,2}))
在列印結果中,不難發現,都有一個__ iter__方法。也就是說要想可迭代,那麼內部必須有一個__iter__方法
那麼,__ iter__方法有什麼用呢?
print([1,2].__iter__())
# 結果:
<list_iterator object at 0x000001D5CF34C898>
又出現一個不懂的單詞iterator,翻譯一下
迭代器
又是一個新問題,什麼是迭代器?
雖然現在還不清楚,但是我們已經有了一個迭代器,我們來研究下這個列表的迭代器和列表的區別.
# 迭代器的方法 - 列表的方法
print(set(dir([1,2].__iter__()))-set(dir([1,2])))
# 結果:
{'__next__', '__length_hint__', '__setstate__'}
可以看到,迭代器中多了三個方法,那麼這三個都分別有什麼用處?
iter_list = [1,2,3,4,5,6].__iter__()
#獲取迭代器中元素的長度
print(iter_list.__length_hint__())
#根據索引值指定從哪裡開始迭代
print(iter_list.__setstate__(4))
#一個一個的取值
print(iter_list.__next__())
print(iter_list.__next__())
在for迴圈中,就是在內部呼叫了__next__方法才能取到一個一個的值。
如果我們一直取next取到迭代器裡已經沒有元素了,就會丟擲一個異常StopIteration,告訴我們,列表中已經沒有有效的元素了。
L = [1,2,3,4]
L_iter = L.__iter__()
while True:
try:
item = L_iter.__next__()
print(item)
except StopIteration:
break
迭代器遵循迭代器協議:必須擁有__iter__方法和__next__方法。
動手試試:看看range()是個啥?
生成器
在 Python 中,使用了 yield 的函式被稱為生成器(generator)。
跟普通函式不同的是,生成器是一個返回迭代器的函式,只能用於迭代操作,更簡單點理解生成器就是一個迭代器。
在呼叫生成器執行的過程中,每次遇到 yield 時函式會暫停並儲存當前所有的執行資訊,返回 yield 的值, 並在下一次執行 next() 方法時從當前位置繼續執行。
呼叫一個生成器函式,返回的是一個迭代器物件。
Python中提供的生成器:
1.生成器函式:常規函式定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函式的狀態,以便下次重它離開的地方繼續執行
2.生成器表示式:類似於列表推導,但是,生成器返回按需產生結果的一個物件,而不是一次構建一個結果列表
生成器Generator:
本質:迭代器(所以自帶了__iter__方法和__next__方法,不需要我們去實現)
特點:惰性運算,開發者自定義
好處:節省記憶體。
栗子:
假如我去飯店吃麵,我上來就要挑戰100碗,老闆娘答應了,然後開始下面給我吃【手動狗頭】,她做一碗,我吃一碗,也可以做3碗,我吃3碗,而不是說等100碗全部做好,在給我,等她做完,面坨了,我也不想吃了(要節約糧食!!!)
def produce():
"""下面"""
for i in range(2000000):
yield "做好第%s份面"%i
product_g = produce()
print(product_g.__next__()) #要一碗
print(product_g.__next__()) #再要一碗
print(product_g.__next__()) #再要一碗
num = 0
for i in product_g: #要五碗
print(i)
num +=1
if num == 5:
break
生成器表示式
l1=[i for i in range(10)] #列表解析
g1=(i for i in range(10)) #生成器表示式
print(g1)
print(next(g1)) #next本質就是呼叫__next__
print(g1.__next__())
print(next(g1))
1.把列表解析的[]換成()得到的就是生成器表示式
2.列表解析與生成器表示式都是一種便利的程式設計方式,只不過生成器表示式更節省記憶體
3.Python不但使用迭代器協議,讓for迴圈變得更加通用。大部分內建函式,也是使用迭代器協議訪問物件的。
面試題
# 面試一
def demo():
for i in range(4):
yield i
g=demo()
g1=(i for i in g)
g2=(i for i in g1)
print(list(g1))
print(list(g2))
# 面試二
def add(n,i):
return n+i
def test():
for i in range(4):
yield i
g=test()
for n in [1,10]:
g=(add(n,i) for i in g)
print(list(g))