叠代器-生成器
叠代器-生成器
楔子:
假如我現在有一個列表l=[‘a‘,‘b‘,‘c‘,‘d‘,‘e‘],我想取列表中的內容,有幾種方法?
首先,我們可以通過索引取值l[0],其次我們是不是還可以用for循環來取值?
你有沒有仔細考慮過,乜可以去到任意位置的值,前前提是你要知道這個值得在什麽位置。
如果用for循環來取值,我們把每一個值都取到,不需要關心每個值的位置,因為只能循序的取值,並不能跳過任何一個直接去取值其他位置的值。
但你有沒有想過,我們為什麽使用for循環來取值?
for 循環內部是怎麽工作的?
叠代器
python 中的for循環
要了解for循環是怎麽回事,咱們還是要從代碼的角度出發。
首先我們對一個列表進行for循環。
for i in [1,2,3,4]:
print(i)
上面這段代碼肯定是沒有問題的,但是我們換一種情況,來循環一個數字1234試試。
for i in 1234:
print(i)
輸出:
Traceback (most recent call last):
File "test.py", line 4, in <module>
for i in 1234:
TypeError: ‘int‘ object is not iterable
報錯了,報了什麽錯?“TypeError: ‘int‘ object is not iterable”,說int類型不是一個iterable,那這個iterable是個啥?
iterable 翻譯:可叠代的,可重復的,叠代的。
假如你不知道什麽是iterable,我們可以翻譯詞典,首先得到一個中文的解釋,盡管翻譯過來了你可能也不知道,但是沒關系,我會帶著你一步一步來分析。
叠代和可叠代協議
什麽叫叠代
現在,我們已經獲得了一個新線索,有一個叫做“可叠代的”概念。
首先,我們從報錯來分析,好像之所以1234不可以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
True
True
True
結合我們使用的for循環取值的現象,再從字面上理解一下魔騎士叠代器就是我們剛剛說的,可以將某個數據集內的數據“一個挨著一個的取出來”,就叫做“叠代器”
可叠代的協議:
我們現在是從結果分析原因,能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__方法做了什麽事情?
print([1,2].__iter__())
輸出:
<list_iterator object at 0x000000C3E6403A90>
執行了list([1,2])的——iter——方法,我們好像得到了一個list——iterator,現在又得到了一個新的名詞--iterator。
iterator 翻譯:叠代器,叠代程序。
iterator,這裏給我們標出來了,是一個計算機中專屬名詞,叫做叠代器。
叠代器協議:
即什麽叫“可叠代”之後,有一個歷史新難題,什麽叫“叠代器“?
雖然我們不著調什麽叫叠代器,但是我們現在已經有一個叠代器了,這個叠代器就是一個列表的叠代器。
我們來看看這個列表的叠代器比起來列表來說實現了那些新方法,這樣就能揭開叠代器的什麽面紗了吧?
dir([1,2].__iter__()) #是列表叠代器中實現的所有方法,dir([1,2]) 是列表中實現的所有方法,都是以列表的形式返回給我們,為了看的更清楚,我們分別把他們轉換成結合,然後取差集。
#print(dir([1,2].__iter__()))
#print(dir([1,2]))
print(set(dir([1,2].__iter__()))-set(dir([1,2])))
輸出:
{‘__length_hint__‘, ‘__next__‘, ‘__setstate__‘}
我們看到在列表叠代器中多了三種方法,那麽這三個方法都分別做了什麽事呢?
iter_l = [1,2,3,4,5,6].__iter__()
print(iter_l.__length_hint__())
print(‘*‘,iter_l.__setstate__(4))
print(‘**‘,iter_l.__next__())
print(‘***‘,iter_l.__next__())
輸出:
6
* None
** 5
*** 6
這三種方法中,能讓我們一個一個取值的神奇方方法是誰?
沒錯!就是__next__
正在for循環中就是在內部調用了__next__方法才能取到一個一個的值。
那接下來我們就用叠代器的next方法來寫一個不依賴for的遍歷。
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)
輸出:
1
2
3
4
那現在我們就使用while循環實現了原本for循環做的事情,我們是從誰那兒獲取一個一個的值呀?是不是就是l_iter?好了,這個l_iter就是一個叠代器。
叠代器遵循叠代器協議:必須擁有__iter__方法和__next__方法。
還賬:next和iter方法
如此一來,關於叠代器和生成器的方法我們就還清了兩個,最後我們來看看range()是個啥。首先,它肯定是一個可叠代的對象,但是它是否是一個叠代器?我們來測試一下
print(‘__next__‘ in dir(range(12))) #查看‘__next__‘是不是在range()方法執行之後內部是否有__next__
print(‘__iter__‘ in dir(range(12))) #查看‘__next__‘是不是在range()方法執行之後內部是否有__next__
from collections import Iterator
print(isinstance(range(100000000),Iterator)) #驗證range執行之後得到的結果不是一個叠代器
為什麽要有for循環
基於上面講的列表這一大堆遍歷方式,聰明的你立馬看出了端倪,於是你不知死活的大聲喊道,你這不逗我玩呢麽,有了下標的訪問方式,我可以這樣遍歷一個列表啊。
l = [1,2,3,]
index=0
while index <len(l):
print(l[index])
index += 1
#要毛線for循環,要毛線可叠代,要毛錢叠代器。
沒錯,序列類型字符串,列表,元組都有下標,你用上述的方式訪問,perfecet!但是你可曾想過非序列類型像字典,集合,文件對象的感受,所以嘛,年輕人,for循環就是基於叠代器協議提供了一個統一的可以遍歷所有對象額方法,即在遍歷之前,先調用對象的__iter__方法將其轉換成一個叠代器,然後使用叠代器協議去實現循環訪問,這樣所有的對象就都可以通過for循環來遍歷了。而且你看到的效果也確實如此,這就是無所不能的for循環,覺悟吧,年輕人。
生成器
初始生成器:
一個包含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))
生成器有什麽好處呢?就是不會一下子在內存中生成太多數據
假如我想讓工廠給做校服,生產20000000件衣服,我和工廠一說,工廠應該是先答應下來,然後再去生產,我可以一件一件的要,也可以根據學生一批一批的找工廠拿。
而不能是一說要生產20000000件衣服工廠就先去做生產20000000件衣服,等回來做好了,學生都畢業了。。
#初識生成器二
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
#到這裏我們找工廠拿了8件衣服,我一共讓我的生產函數(也就是produce生成器函數)生產2000000件衣服。
#剩下的還有很多衣服,我們可以一直拿,也可以放著等想拿的時候再拿
更多應用:
import time
def tail(filename):
f = open(filename)
f.seek(0, 2) #從文件末尾算起
while True:
line = f.readline() # 讀取文件中新的文本行
if not line:
time.sleep(0.1)
continue
yield line
tail_g = tail(‘tmp‘)
for line in tail_g:
print(line)
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不能接受外部的值
計算移動平均值:
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total/count
g_avg = averager()
next(g_avg)
print(g_avg.send(10))
print(g_avg.send(30))
print(g_avg.send(5))
計算移動平均值(2)_預激協程的裝飾器
def init(func): #在調用被裝飾生成器函數的時候首先用next激活生成器
def inner(*args,**kwargs):
g = func(*args,**kwargs)
next(g)
return g
return inner
@init
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total/count
g_avg = averager()
# next(g_avg) 在裝飾器中執行了next方法
print(g_avg.send(10))
print(g_avg.send(30))
print(g_avg.send(5))
yield from
def gen1():
for c in ‘AB‘:
yield c
for i in range(3):
yield i
print(list(gen1()))
def gen2():
yield from ‘AB‘
yield from range(3)
print(list(gen2()))
列表推導式和生成器表達式
#老男孩由於峰哥的強勢加盟很快走上了上市之路,alex思來想去決定下幾個雞蛋來報答峰哥
egg_list=[‘雞蛋%s‘ %i for i in range(10)] #列表解析
#峰哥瞅著alex下的一筐雞蛋,捂住了鼻子,說了句:哥,你還是給我只母雞吧,我自己回家下
laomuji=(‘雞蛋%s‘ %i for i in range(10))#生成器表達式
print(laomuji)
print(next(laomuji)) #next本質就是調用__next__
print(laomuji.__next__())
print(next(laomuji))
總結:
1.把列表解析的[]換成()得到的就是生成器表達式
2.列表解析與生成器表達式都是一種便利的編程方式,只不過生成器表達式更節省內存
3.Python不但使用叠代器協議,讓for循環變得更加通用。大部分內置函數,也是使用叠代器協議訪問對象的。例如, sum函數是Python的內置函數,該函數使用叠代器協議訪問對象,而生成器實現了叠代器協議,所以,我們可以直接這樣計算一系列值的和:
sum(x ** 2 for x in range(4))
而不用多此一舉的先構造一個列表:
sum([x ** 2 for x in range(4)])
叠代器-生成器