1. 程式人生 > >Python之叠代器及生成器

Python之叠代器及生成器

滿足 subclass 毛線 總結 繼續 異常處理 instance 好處 應該

一. 叠代器

1.1 什麽是可叠代對象

字符串、列表、元組、字典、集合 都可以被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))

1.2 可叠代協議

我們現在是從結果分析原因,能被for循環的就是“可叠代的”,但是如果正著想,for怎麽知道誰是可叠代的呢?

假如我們自己寫了一個數據類型,希望這個數據類型裏的東西也可以使用for被一個一個的取出來,那我們就必須滿足for的要求。這個要求就叫做“協議”。

可以被叠代要滿足的要求就叫做可叠代協議。可叠代協議的定義非常簡單,就是內部實現了__iter__方法

接下來我們就來驗證一下:

技術分享圖片
print(dir([1,2]))
print(dir((2,3)))
print(dir({1:2}))
print
(dir({1,2})) 結果: [__add__, __class__, __contains__, __delattr__, __delitem__, __dir__, __doc__, __eq__, __format__, __ge__, __getattribute__, __getitem__, __gt__, __hash__, __iadd__, __imul__, __init__, __iter__, __le__, __len__, __lt__, __mul__, __ne__
, __new__, __reduce__, __reduce_ex__, __repr__, __reversed__, __rmul__, __setattr__, __setitem__, __sizeof__, __str__, __subclasshook__, append, clear, copy, count, extend, index, insert, pop, remove, reverse, sort] [__add__, __class__, __contains__, __delattr__, __dir__, __doc__, __eq__, __format__, __ge__, __getattribute__, __getitem__, __getnewargs__, __gt__, __hash__, __init__, __iter__, __le__, __len__, __lt__, __mul__, __ne__, __new__, __reduce__, __reduce_ex__, __repr__, __rmul__, __setattr__, __sizeof__, __str__, __subclasshook__, count, index] [__class__, __contains__, __delattr__, __delitem__, __dir__, __doc__, __eq__, __format__, __ge__, __getattribute__, __getitem__, __gt__, __hash__, __init__, __iter__, __le__, __len__, __lt__, __ne__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __setitem__, __sizeof__, __str__, __subclasshook__, clear, copy, fromkeys, get, items, keys, pop, popitem, setdefault, update, values] [__and__, __class__, __contains__, __delattr__, __dir__, __doc__, __eq__, __format__, __ge__, __getattribute__, __gt__, __hash__, __iand__, __init__, __ior__, __isub__, __iter__, __ixor__, __le__, __len__, __lt__, __ne__, __new__, __or__, __rand__, __reduce__, __reduce_ex__, __repr__, __ror__, __rsub__, __rxor__, __setattr__, __sizeof__, __str__, __sub__, __subclasshook__, __xor__, add, clear, copy, difference, difference_update, discard, intersection, intersection_update, isdisjoint, issubset, issuperset, pop, remove, symmetric_difference, symmetric_difference_update, union, update]
驗證結果

總結一下我們現在所知道的:可以被for循環的都是可叠代的,要想可叠代,內部必須有一個__iter__方法。

接著分析,__iter__方法做了什麽事情呢?

可叠代的:內部必須含有一個__iter__方法。

1.3 叠代器

什麽叫做叠代器?叠代器英文意思是iterator

l = [1,2,3,4]
l_iter = l.__iter__()  # 將可叠代的轉化成叠代器
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)

叠代器遵循叠代器協議:必須擁有__iter__方法和__next__方法

for循環,能遍歷一個可叠代對象,他的內部到底進行了什麽?

  • 將可叠代對象轉化成叠代器。(可叠代對象.__iter__())
  • 內部使用__next__方法,一個一個取值。
  • 加了異常處理功能,取值到底後自動停止。

用while循環模擬for循環:

l = [1,2,3,4]
l_iter = l.__iter__()
while True:
    try:
        item = l_iter.__next__()
        print(item)
    except StopIteration:
        break

1.4為什麽要有for循環

基於上面講的列表這一大堆遍歷方式,聰明的你立馬看除了端倪,於是你不知死活大聲喊道,你這不逗我玩呢麽,有了下標的訪問方式,我可以這樣遍歷一個列表啊

l=[1,2,3]

index=0
while index < len(l):
    print(l[index])
    index+=1

#要毛線for循環,要毛線可叠代,要毛線叠代器

沒錯,序列類型字符串,列表,元組都有下標,你用上述的方式訪問,perfect!但是你可曾想過非序列類型像字典,集合,文件對象的感受,所以嘛,年輕人,for循環就是基於叠代器協議提供了一個統一的可以遍歷所有對象的方法,即在遍歷之前,先調用對象的__iter__方法將其轉換成一個叠代器,然後使用叠代器協議去實現循環訪問,這樣所有的對象就都可以通過for循環來遍歷了,而且你看到的效果也確實如此,這就是無所不能的for循環,最重要的一點,轉化成叠代器,在循環時,同一時刻在內存中只出現一條數據,極大限度的節省了內存~

二. 生成器

2.1初始生成器

我們知道的叠代器有兩種:一種是調用方法直接返回的,一種是可叠代對象通過執行iter方法得到的,叠代器有的好處是可以節省內存。

如果在某些情況下,我們也需要節省內存,就只能自己寫。我們自己寫的這個能實現叠代器功能的東西就叫生成器。

Python中提供的生成器:

1.生成器函數:常規函數定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函數的狀態,以便下次重它離開的地方繼續執行

2.生成器表達式:類似於列表推導,但是,生成器返回按需產生結果的一個對象,而不是一次構建一個結果列表

生成器Generator:

  本質:叠代器(所以自帶了__iter__方法和__next__方法,不需要我們去實現)

  特點:惰性運算,開發者自定義

2.2 生成器函數

一個包含yield關鍵字的函數就是一個生成器函數。yield可以為我們從函數中返回值,但是yield又不同於return,return的執行意味著程序的結束,調用生成器函數不會得到返回的具體的值,而是得到一個可叠代的對象。每一次獲取這個可叠代對象的值,就能推動函數的執行,獲取新的返回值。直到函數執行結束。

技術分享圖片
import time
def genrator_fun1():
    a = 1
    print(現在定義了a變量)
    yield a
    b = 2
    print(現在又定義了b變量)
    yield b

g1 = genrator_fun1()
print(g1 : ,g1)       #打印g1可以發現g1就是一個生成器
print(-*20)   #我是華麗的分割線
print(next(g1))
time.sleep(1)   #sleep一秒看清執行過程
print(next(g1))

初識生成器函數
初始生成器函數

生成器有什麽好處呢?就是不會一下子在內存中生成太多數據

假如我想讓工廠給學生做校服,生產2000000件衣服,我和工廠一說,工廠應該是先答應下來,然後再去生產,我可以一件一件的要,也可以根據學生一批一批的找工廠拿。
而不能是一說要生產2000000件衣服,工廠就先去做生產2000000件衣服,等回來做好了,學生都畢業了。。。

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:         #要一批衣服,比如5件
    print(i)
    num +=1
    if num == 5:
        break

2.3 send

def generator():
    print(123)
    content = yield 1
    print(‘=======‘,content)
    print(456)
    yield2

g = generator()
ret = g.__next__()
print(‘***‘,ret)
ret = g.send(‘hello‘)   #send的效果和next一樣
print(‘***‘,ret)

#send 獲取下一個值的效果和next基本一致
#只是在獲取下一個值的時候,給上一yield的位置傳遞一個數據
#使用send的註意事項
    # 第一次使用生成器的時候 是用next獲取下一個值
    # 最後一個yield不能接受外部的值

三. 列表推導式和生成器表達式

l = [i for i in range(10)]
print(l)
l1 = [選項%s%i for i in range(10)]
print(l1)

1.把列表解析的[]換成()得到的就是生成器表達式

2.列表解析與生成器表達式都是一種便利的編程方式,只不過生成器表達式更節省內存

3.Python不但使用叠代器協議,讓for循環變得更加通用。大部分內置函數,也是使用叠代器協議訪問對象的。例如, sum函數是Python的內置函數,該函數使用叠代器協議訪問對象,而生成器實現了叠代器協議,所以,我們可以直接這樣計算一系列值的和:

sum(x ** 2 for x in range(4))

Python之叠代器及生成器