1. 程式人生 > 實用技巧 >10-Python之路---迭代器和生成器

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))