day4-python之迭代器和生成器

 

1、引言python

有一個列表l = ['a','b','c','d','e'],想取列表中內容有幾種方式?程序員

一、索引取值l[0]面試

二、for循環取值編程

那麼,索引取值和for循環取值的區別是什麼。數組

若是索引取值必需要知道這個值在什麼位置。app

若是for循環取值,把每一個值都取到,不須要關心每一個值的位置,由於for循環只能按順序取值,不能跳過任何一個值直接去取其餘位置的值。ssh

可是for循環的內部是怎麼工做的呢?爲何可使用for循環來取值?ide

2、迭代器函數

首先,對一個列表進行for循環,確定是沒問題大數據

1 for i in [1,2,3,4]: 2     print(i)

其次,對一個數字1234進行for循環,會報錯int類型不是一個iterable(可迭代的)

 1 for i in 1234:  2     print(i)  3 
 4 # 結果:
 5 '''
 6 Traceback (most recent call last):  7  File "C:/Users/benjamin/python自動化21期/day4/00 day4 test.py", line 87, in <module>  8  for i in 1234:  9 TypeError: 'int' object is not iterable 10 '''

 

一、什麼叫迭代?

將某個數據集內的數據「一個挨着一個的取出來」,就叫作迭代。

 1 from collections import Iterable  2 
 3 l = [1, 2, 3, 4]  4 t = (1, 2, 3, 4)  5 d = {1: 2, 3: 4}  6 s = {1, 2, 3, 4}  7 
 8 print(isinstance(l, Iterable))  9 print(isinstance(t, Iterable)) 10 print(isinstance(d, Iterable)) 11 print(isinstance(s, Iterable))

字符串、列表、元組、集合、字典均可以被for循環,說明這些都是可迭代的。

二、可迭代協議

能夠被迭代要求知足的就叫作可迭代協議。可迭代協議的定義就是內部實現了__iter__方法。

1 print(dir([1,2])) 2 print(dir((2,3))) 3 print(dir({1:2})) 4 print(dir({1,2}))
1 ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__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'] 2 ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index'] 3 ['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__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'] 4 ['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__init_subclass__', '__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__方法。

1 print([1,2].__iter__()) 2 
3 # 結果:<list_iterator object at 0x0000026D22A59320>

獲得一個list_iterator(列表迭代器)

 

三、迭代器協議

1 # dir([1,2]) 是列表中實現的全部方法,dir([1,2].__iter__())是列表迭代器中實現的全部方法,都是以列表的形式返回的。爲了看得更清楚,分別將他們轉換成集合,而後取差集。
2 # print(dir([1,2])) # 查看列表的全部方法
3 # print(dir([1,2].__iter__())) # 查看列表迭代器的全部方法
4 print(set(dir([1,2].__iter__()))-set(dir([1,2]))) 5 
6 # 結果:{'__setstate__', '__next__', '__length_hint__'}

三個方法的做用:

 1 iter_l = [1,2,3,4,5,6].__iter__()  2 
 3 # 獲取迭代器中元素的長度
 4 print(iter_l.__length_hint__())  5 
 6 # 根據索引值指定從哪裏開始迭代
 7 print('*',iter_l.__setstate__(4))  8 
 9 # 一個一個的取值
10 print('**',iter_l.__next__()) 11 print('***',iter_l.__next__())

在for循環中,就是在內部調用了__next__方法才能取到一個一個的值。

用迭代器next方法來寫一個不依賴for的遍歷:

 1 l = [1,2,3,4]  2 l_iter = l.__iter__()  3 item = l_iter.__next__()  4 print(item)  5 item = l_iter.__next__()  6 print(item)  7 item = l_iter.__next__()  8 print(item)  9 item = l_iter.__next__() 10 print(item) 11 item = l_iter.__next__() 12 print(item)

這裏會拋出一個異常StopIteration,告訴咱們列表已經沒有有效的元素。

這時,就要使用異常處理機制把這個異常處理掉。

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

while循環從l_iter獲取一個一個的值,那麼l_iter就是一個迭代器。

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

1 print('__next__' in dir(range(12)))  #查看'__next__'是否是在range()方法執行以後內部是否有__next__
2 print('__iter__' in dir(range(12)))  #查看'__next__'是否是在range()方法執行以後內部是否有__next__
3 
4 from collections import Iterator 5 print(isinstance(range(100000000),Iterator))  #驗證range執行以後獲得的結果不是一個迭代器
range函數的返回值是一個可迭代對象

python2的range無論range多少,會生成一個列表,這個列表將用來存儲全部的值(工做全作完才彙報)

python3的range無論range多少,都不會實際的生成任何一個值,只有要的時候纔會從迭代器裏面生成(每作一步都彙報)

迭代器的優點:

        節省內存

         取一個值就能進行接下來的計算,不須要等到全部值都計算出來纔開始接下來的計算 —— 快

迭代器的特性:惰性運算

列表  字典  元組  字符串  集合  range  文件句柄  enumerate

四、爲何要有for循環

有了下標的訪問方式,能夠這樣遍歷一個列表:

1 l=[1,2,3] 2 
3 index=0 4 while index < len(l): 5     print(l[index]) 6     index+=1

序列類型字符串、列表、元組都有下標,能夠用上面方式訪問。可是非序列類型字典、集合、文件對象就不能夠了。因此,for循環就是基於迭代器協議提供了一個統一的能夠遍歷全部對象的方法,即在遍歷以前,先調用對象的__iter__方法將其轉換成一個迭代器,而後使用迭代器協議去實現循環訪問,這樣全部的對象就均可以經過for循環來遍歷。

 3、生成器

 一、迭代器有兩種:

1)調用方法直接返回的

2)可迭代對象經過執行iter方法獲得的

迭代器的最大好處就是節省內存,而爲了節省內存,本身寫的能實現迭代器功能的東西就叫作生成器。

二、python中提供的生成器:

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

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

三、生成器Generator:

本質:迭代器(因此自帶了__iter__方法和__next__方法,不須要咱們去實現)

特色:惰性運算、開發者自定義

 

四、生成器函數

一個包含yield關鍵字的函數就是一個生成器函數。yield能夠爲咱們從函數中返回值,可是yield又不一樣於return,return的執行意味着程序的結束,調用生成器函數不會獲得返回的具體的值,而是獲得一個可迭代的對象。每一次獲取這個可迭代對象的值,就能推進函數的執行,獲取新的返回值。直到函數執行結果。

 1 import time  2 def genrator_fun1():  3     a = 1
 4     print('如今定義了a變量')  5     yield a  6     b = 2
 7     print('如今又定義了b變量')  8     yield b  9 
10 g1 = genrator_fun1() 11 print('g1 : ',g1)       #打印g1能夠發現g1就是一個生成器
12 print('-'*20)   #我是華麗的分割線
13 print(next(g1)) 14 time.sleep(1)   #sleep一秒看清執行過程
15 print(next(g1))
初識生成器函數

生成器的好處,就是不會一會兒在內存中生成太多數據。

例1:生成器函數,工廠作校服

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

 1 def produce():  2     """生產衣服"""
 3     for i in range(2000000):  4         yield "生產了第%s件衣服"%i  5 
 6 product_g = produce()  7 print(product_g.__next__()) #要一件衣服
 8 print(product_g.__next__()) #再要一件衣服
 9 print(product_g.__next__()) #再要一件衣服
10 num = 0 11 for i in product_g:         #要一批衣服,好比5件
12     print(i) 13     num +=1
14     if num == 5: 15         break
16 
17 #到這裏咱們找工廠拿了8件衣服,我一共讓個人生產函數(也就是produce生成器函數)生產2000000件衣服。
18 #剩下的還有不少衣服,咱們能夠一直拿,也能夠放着等想拿的時候再拿
初識生成器二
生成器監聽文件輸入

五、send

 1 def generator():  2     print(123)  3     content = yield 1
 4     print('=======',content)  5     print(456)  6     yield 2
 7 
 8 g = generator()  9 ret = g.__next__() 10 print('***',ret) 11 ret = g.send('hello')   #send的效果和next同樣
12 rint('***',ret) 13 
14 #send 獲取下一個值的效果和next基本一致
15 #只是在獲取下一個值的時候,給上一yield的位置傳遞一個數據
16 #使用send的注意事項
17     # 第一次使用生成器的時候 是用next獲取下一個值
18     # 最後一個yield不能接受外部的值

 # 計算移動平均值

# 12  13  15  18

# 月度 的 天平均收入

 1 # 必須先用next再用send
 2 def average():  3     total=0 #總數
 4     day=0 #天數
 5     average=0 #平均數
 6     while True:  7         day_num = yield average   #average=0
 8         total += day_num  9         day += 1
10         average = total/day 11 avg=average() #直接返回生成器
12 next(avg)#激活生成器,avg.send(),什麼都不傳的時候send和next的效果同樣
13 print(avg.send(10)) 14 print(avg.send(20))#send 1.傳值 2.next
15 print(avg.send(30))
計算移動平均值
 1 # 讓裝飾器去激活
 2 def wrapper(func):  3     def inner(*args,**kwargs):  4        ret = func(*args,**kwargs)  5  next(ret)  6        return ret  7     return inner  8 
 9 @wrapper 10 def average(): 11     total=0 #總數
12     day=0 #天數
13     average=0 #平均數
14     while True: 15         day_num = yield average   #average=0
16         total += day_num 17         day += 1
18         average = total/day 19 
20 
21 ret=average() #直接返回生成器
22 print(ret.send(10)) 23 print(ret.send(20))#send 1.傳一個值過去 2.讓當前yield繼續執行
24 print(ret.send(30))
帶裝飾器的計算移動平均值

 七、yield from

 1 def gen1():  2     for c in 'AB':  3         yield c  4     for i in range(3):  5         yield i  6 
 7 print(list(gen1()))  8 
 9 def gen2(): 10     yield from 'AB'
11     yield from range(3) 12 
13 print(list(gen2()))
yield from

 如何從生成器中取值

一、next  隨時能夠中止,最後一次會報錯

print(next(g))

print(next(g))

二、for循環  從頭至尾遍歷一次,不遇到break,函數裏面不遇到return不會中止

for i in g:

  print(i)

三、list、tuple  數據類型的強轉,會把全部的數據都加載到內存裏,很是浪費內存

print(g)

print(list(g))

 總結:

# 生成器函數 是咱們python程序員實現迭代器的一種手段
# 主要特徵是 在函數中 含有yield
# 調用一個生成器函數 不會執行這個函數中的帶碼 只是會得到一個生成器(迭代器)
# 只有從生成器中取值的時候,纔會執行函數內部的帶碼,且每獲取一個數據才執行獲得這個數據的帶碼
# 獲取數據的方式包括 next send 循環 數據類型的強制轉化
# yield返回值的簡便方法,若是自己就是循環一個可迭代的,且要把可迭代數據中的每個元素都返回 能夠用yield from
# 使用send的時候,在生成器創造出來以後須要進行預激,這一步可使用裝飾器完成
# 生成器的特色 : 節省內存 惰性運算
# 生成器用來解決 內存問題 和程序功能之間的解耦

4、列表推導式

 1 例1:  2 # for循環
 3 y = 2
 4 for i in range(100):  5     print( i * y )  6 
 7 # 列表推導式
 8 y = 2
 9 l = [ i * y for i in range(100)] 10 print(l) 11 
12 例2: 13 # for循環
14 l=[{'name':'v1','age':'22'},{'name':'v2'}] 15 for dic in l: 16     print(dic['name']) 17 
18 # 列表推導式
19 l=[{'name':'v1','age':'22'},{'name':'v2'}] 20 name_list=[dic['name'] for dic in l] 21 print(name_list)
列表推導式
 1 # ======一層循環======
 2 l = [i*i for i in range(1,10)]  3 print(l)  4 # 上面的列表推倒式就至關於下面的
 5 l  = []  6 for i in range(1,10):  7     l.append(i*i)  8 print(l)  9 l = [] 10 
11 
12 # ======多層循環========
13 # 1.列表推倒式
14 l = [i*j for i in range(1,10) for j in range(1,10)] 15 print(l) 16 # 2.循環
17 l = [] 18 for i in range(1,10): 19     for j in range(1,10): 20         s = i*j 21  l.append(s) 22 print(l)
列表推導式

 總結:

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

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

3)python不但使用迭代器協議,讓for循環變得更加通用。大部份內置函數,也是使用迭代器協議訪問對象的。例如,sum函數是python的內置函數,該函數使用迭代器協議訪問對象,而生成器實現了迭代器協議,因此,咱們能夠直接這樣計算一系列值的和:

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

而不用畫蛇添足的先構造一個列表:

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

5、推導式詳解

列表推導式、生成器表達式、字典推導式、集合推導式等等。

1 variable = [out_exp_res for out_exp in input_list if out_exp == 2] 2  out_exp_res:  列表生成元素表達式,能夠是有返回值的函數。 3   for out_exp in input_list:  迭代input_list將out_exp傳入out_exp_res表達式中。 4   if out_exp == 2:  根據條件過濾哪些值能夠。

一、列表推導式

例1:30之內全部能被3整除的數

1 multiples = [i for i in range(30) if i % 3 is 0] 2 print(multiples) 3 # Output: [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]
列表推導式1

例2:30之內全部能被3整除的數的平方

1 def squared(x): 2     return x*x 3 multiples = [squared(i) for i in range(30) if i % 3 is 0] 4 print(multiples)
列表推導式2

例3:找到嵌套列表中名字含有兩個‘e’的全部名字

1 names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'], 2          ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']] 3 
4 print([name for lst in names for name in lst if name.count('e') >= 2])  # 注意遍歷順序,這是實現的關鍵
列表推導式3

二、字典推導式

例1:將一個字典的key和value對調

1 mcase = {'a': 10, 'b': 34} 2 mcase_frequency = {mcase[k]: k for k in mcase} 3 print(mcase_frequency)
字典推導式1

例2:合併大小寫對應的value值,將k統一成小寫

1 mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3} 2 mcase_frequency = {k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0) for k in mcase.keys()} 3 print(mcase_frequency)
字典推導式2

三、集合推導式

例:計算列表中每一個值的平方,自帶去重功能

1 squared = {x**2 for x in [1, -1, 2]} 2 print(squared) 3 # Output: set([1, 4])
集合推導式

 練習題:

例1:  過濾掉長度小於3的字符串列表,並將剩下的轉換成大寫字母

例2:  求(x,y)其中x是0-5之間的偶數,y是0-5之間的奇數組成的元祖列表

例3:  求M中3,6,9組成的列表M = [[1,2,3],[4,5,6],[7,8,9]]

1 [name.upper() for name in names if len(name)>3] 2 [(x,y) for x in range(5) if x%2==0 for y in range(5) if y %2==1] 3 [row[2] for row in M] 
練習題

6、總結

可迭代對象:
  擁有__iter__方法
  特色:惰性運算
  例如:range(),str,list,tuple,dict,set
迭代器Iterator:
  擁有__iter__方法和__next__方法
  例如:iter(range()),iter(str),iter(list),iter(tuple),iter(dict),iter(set),reversed(list_o),map(func,list_o),filter(func,list_o),file_o
生成器Generator:
  本質:迭代器,因此擁有__iter__方法和__next__方法
  特色:惰性運算,開發者自定義
使用生成器的優勢:
1.延遲計算,一次返回一個結果。也就是說,它不會一次生成全部的結果,這對於大數據量處理,將會很是有用。

1 #列表解析
2 sum([i for i in range(100000000)])#內存佔用大,機器容易卡死
3 #生成器表達式
4 sum(i for i in range(100000000))#幾乎不佔內存
列表解析式和生成器表達式

2.提升代碼可讀性

# 生成器和迭代器

# 迭代器 : iter next
# 能夠被for循環 節省內存空間 它沒有所謂的索引取值的概念 當前的值和下一個值- 公式
# 生成器和迭代器本質上是同樣的
# yield函數
# 執行生成器函數 會獲得一個生成器 不會執行這個函數中的代碼
# 有幾個yield,就能從中取出多少個值
# 生成器表達式
# 生成器表達式也會返回一個生成器 也不會直接被執行
# for循環裏有幾個符合條件的值生成器就返回多少值
# 每個生成器都會從頭開始取值,當取到最後的時候,生成器中就沒有值了
# 一個生成器只能用一次
 1 def fff():
 2     for i in range(10):
 3         yield i
 4 g2 = (i**i for i in range(20))
 5 g = fff()
 6 print(next(g))
 7 print(next(g))
 8 print(next(g))
 9 
10 print(next(fff()))
11 print(next(fff()))
12 print(next(fff()))
13 for i in fff():
14     print(i)

7、生成器相關面試題

 1 def demo():  2     for i in range(4):  3         yield i  4 
 5 g=demo()  6 
 7 g1=(i for i in g)  8 g2=(i for i in g1)  9 
10 print(list(g1)) 11 print(list(g2))
面試題1

 1 def add(n,i):  2     return n+i  3 
 4 def test():  5     for i in range(4):  6         yield i  7 
 8 g=test()  9 for n in [1,10]: 10     g=(add(n,i) for i in g) 11 
12 print(list(g))
面試題2
 1 import os  2 
 3 def init(func):  4     def wrapper(*args,**kwargs):  5         g=func(*args,**kwargs)  6  next(g)  7         return g  8     return wrapper  9 
10 @init 11 def list_files(target): 12     while 1: 13         dir_to_search=yield
14         for top_dir,dir,files in os.walk(dir_to_search): 15             for file in files: 16  target.send(os.path.join(top_dir,file)) 17 @init 18 def opener(target): 19     while 1: 20         file=yield
21         fn=open(file) 22  target.send((file,fn)) 23 @init 24 def cat(target): 25     while 1: 26         file,fn=yield
27         for line in fn: 28  target.send((file,line)) 29 
30 @init 31 def grep(pattern,target): 32     while 1: 33         file,line=yield
34         if pattern in line: 35  target.send(file) 36 @init 37 def printer(): 38     while 1: 39         file=yield
40         if file: 41             print(file) 42 
43 g=list_files(opener(cat(grep('python',printer())))) 44 
45 g.send('/test1') 46 
47 協程應用:grep -rl /dir
tail&grep
# 一個生成器 只能取一次
# 生成器在不找它要值的時候始終不執行
# 當他執行的時候,要以執行時候的全部變量值爲準
相關文章
相關標籤/搜索