天天看點

15 Python 疊代器和生成器各種推導式詳解

什麼是疊代 (iterable)

  字元串、清單、元組、字典、集合都可以被for循環,說明他們都是可疊代的。

  可以直接作用于for循環的對象統稱為可疊代對象(Iterable)。

  可以被next()函數調用并不斷傳回下一個值的對象稱為疊代器(Iterator)。

  所有的Iterable均可以通過内置函數iter()來轉變為Iterator。

  對疊代器來講,有一個__next__()就夠了。在你使用for 和 in 語句時,程式就會自動調用即将被處理的對象的疊代器對象,然後使用它的__next__()方法,直到監測到一個StopIteration異常。

  可疊代協定

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

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

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

1 >>> L = [1,2,3]
 2 >>> [x**2 for x in L]
 3 [1, 4, 9]
 4 >>> next(L)
 5 Traceback (most recent call last):
 6   File "<stdin>", line 1, in <module>
 7 TypeError: 'list' object is not an iterator
 8 >>> I=iter(L)
 9 >>> next(I)
10 1
11 >>> next(I)
12 2
13 >>> next(I)
14 3
15 >>> next(I)
16 Traceback (most recent call last):
17   File "<stdin>", line 1, in <module>
18 StopIteration      

View Code

  上面例子中,清單L可以被for進行循環但是不能被内置函數next()用來查找下一個值,是以L是Iterable。

  L通過iter進行包裝後設為I,I可以被next()用來查找下一個值,是以I是Iterator。

  1. 内置函數iter()僅僅是調用了對象的__iter()方法,是以list對象内部一定存在方法iter__()
  2. 内置函數next()僅僅是調用了對象的__next()方法,是以list對象内部一定不存在方法next__(),但是Itrator中一定存在這個方法。
  3. for循環内部事實上就是先調用iter()把Iterable變成Iterator在進行循環疊代的
1 >>> L = [4,5,6]
 2 >>> I = L.__iter__()
 3 >>> L.__next__()
 4 Traceback (most recent call last):
 5   File "<stdin>", line 1, in <module>
 6 AttributeError: 'list' object has no attribute '__next__'
 7 >>> I.__next__()
 8 4
 9 >>> from collections import Iterator, Iterable
10 >>> isinstance(L, Iterable)
11 True
12 >>> isinstance(L, Iterator)
13 False
14 >>> isinstance(I, Iterable)
15 True
16 >>> isinstance(I, Iterator)
17 True
18 >>> [x**2 for x in I]    
19 [25, 36]      

View Code

疊代器

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

具有通路生成器的能力,可以通路到生成器的值,類似于生成器的

__next__

方法,一個一個值一個值得去疊代,隻能夠按照順序的去查找。

特點:

  1. 通路者不需要關心疊代器内部的結構,僅需通過next()方法不斷去取下一個内容
  2. 不能随機通路集合中的某個值 ,隻能從頭到尾依次通路
  3. 通路到一半時不能往回退
  4. 便于循環比較大的資料集合,節省記憶體
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執行之後得到的結果不是一個疊代器      

View Code

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

View Code

生成器

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

  2.生成器表達式:類似于清單推導,但是,生成器傳回按需産生結果的一個對象,而不是一次建構一個結果清單

生成器Generator:

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

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

生成器函數

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

  僅僅擁有生成某種東西的能力,如果不用

__next__

方法是擷取不到值得。

  建立一個生成器函數

1 >>> def scq():
 2     print("11")
 3        # 當函數代碼塊中遇到yield關鍵字的時候,這個函數就是一個生成器函數
 4     yield 1
 5     print("22")
 6     yield 2
 7     print("33")
 8     yield 3
 9 
10 
11 # 把生成器指派給一個對象
12 
13 >>> r = scq()
14 
15 # 檢視r的蘇劇類型并且輸出r的值
16 
17 >>> print(type(r),r)
18 <class 'generator'> <generator object scq at 0x000001F117D8DF10> 
19 
20 # 當執行生成器的__next__的時候,代碼會按照順序去執行,當執行到yield時會傳回并
21 # 提出,yield後面的值就是傳回值,然後記錄代碼執行的位置,并退出
22 
23 >>> ret = r.__next__()
24 11
25 
26 # 第二次執行的時候會根據上次代碼執行的位置繼續往下執行
27 
28 >>> ret = r.__next__()
29 22
30 >>> ret = r.__next__()
31 33
32 
33 # 如果__next__擷取不到值的時候就會報StopIteration錯誤
34 
35 >>> ret = r.__next__()
36 Traceback (most recent call last):
37   File "<stdin>", line 1, in <module>
38 StopIteration      

View Code

  利用生成器建立一個range

1 # 建立一個生成器函數,函數名是range,n是傳入的參數,也是輸出的數的最大值
 2 def range(n):
 3     # 預設從0開始
 4     start = 0
 5     # 進入while循環,如果最小值小于最大值就進入循環
 6     while start < n:
 7         # 第一次傳回start,下面代碼不執行
 8         yield start
 9         # 第二次進來的時候start = start + 1,然後進入下一次循環
10         start += 1
11 
12 # 停止的參數為5
13 obj = range(5)
14 # 第一個數指派給n1
15 n1 = obj.__next__()
16 # 第二個數指派給n2
17 n2 = obj.__next__()
18 # 第三個數指派給n3
19 n3 = obj.__next__()
20 # 第四個數指派給n4
21 n4 = obj.__next__()
22 # 第五個數指派給n5
23 n5 = obj.__next__()
24 
25 # 輸出這五個數的值
26 print(n1,n2,n3,n4,n5)
27 
28 # 執行結果
29 
30 C:\Python35\python.exe F:/Python_code/sublime/Week5/Day03/s1.py
31 0 1 2 3 4
32 
33 Process finished with exit code 0      

View Code

  生成器監聽檔案輸入的例題

1 import time
 2 
 3 
 4 def tail(filename):
 5     f = open(filename)
 6     f.seek(0, 2) #從檔案末尾算起
 7     while True:
 8         line = f.readline()  # 讀取檔案中新的文本行
 9         if not line:
10             time.sleep(0.1)
11             continue
12         yield line
13 
14 tail_g = tail('tmp')
15 for line in tail_g:
16     print(line)      

View Code

 計算移動平均值(1)

1 def averager():
 2     total = 0.0
 3     count = 0
 4     average = None
 5     while True:
 6         term = yield average
 7         total += term
 8         count += 1
 9         average = total/count
10 
11 
12 g_avg = averager()
13 next(g_avg)
14 print(g_avg.send(10))
15 print(g_avg.send(30))
16 print(g_avg.send(5))      

View Code

 計算移動平均值(2)預激協程的裝飾器

1 def init(func):  #在調用被裝飾生成器函數的時候首先用next激活生成器
 2     def inner(*args,**kwargs):
 3         g = func(*args,**kwargs)
 4         next(g)
 5         return g
 6     return inner
 7 
 8 @init
 9 def averager():
10     total = 0.0
11     count = 0
12     average = None
13     while True:
14         term = yield average
15         total += term
16         count += 1
17         average = total/count
18 
19 
20 g_avg = averager()
21 # next(g_avg)   在裝飾器中執行了next方法
22 print(g_avg.send(10))
23 print(g_avg.send(30))
24 print(g_avg.send(5))      

View Code

清單推導式和生成器表達式

1 #老男孩由于峰哥的強勢加盟很快走上了上市之路,alex思來想去決定下幾個雞蛋來報答峰哥
 2 
 3 egg_list=['雞蛋%s' %i for i in range(10)] #清單解析
 4 
 5 #峰哥瞅着alex下的一筐雞蛋,捂住了鼻子,說了句:哥,你還是給我隻母雞吧,我自己回家下
 6 
 7 laomuji=('雞蛋%s' %i for i in range(10))#生成器表達式
 8 print(laomuji)
 9 print(next(laomuji)) #next本質就是調用__next__
10 print(laomuji.__next__())
11 print(next(laomuji))      

View Code

各種推導式詳解

  推導式的套路

    之前我們已經學習了最簡單的清單推導式和生成器表達式。但是除此之外,其實還有字典推導式、集合推導式等等。

    下面是一個以清單推導式為例的推導式詳細格式,同樣适用于其他推導式。

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:  根據條件過濾哪些值可以。      

View Code

清單推導式

  例一: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]      

View Code

  例二: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)      

View Code

  例三:找到嵌套清單中名字含有兩個‘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])  # 注意周遊順序,這是實作的關鍵      

View Code

字典推導式

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

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

View Code

  例二:合并大小寫對應的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)      

View Code

集合推導式

  例:計算清單中每個值的平方,自帶去重功能

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

View Code

練習題:

  例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 1.[name.upper() for name in names if len(name)>3] 
2 2.[(x,y) for x in range(5) if x%2==0 for y in range(5) if y %2==1] 
3 3. [row[2] for row in M]      

View Code

總結:

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

  2.清單解析與生成器表達式都是一種便利的程式設計方式,隻不過生成器表達式更節省記憶體

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

1 sum(x ** 2 for x in range(4))
2 
3 # 而不用多此一舉的先構造一個清單:
4 
5 sum([x ** 2 for x in range(4)])       

View Code

可疊代對象:

  擁有__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.延遲計算,一次傳回一個結果。也就是說,它不會一次生成所有的結果,這對于大資料量處理,将會非常有用。

2.提高代碼可讀性

1 #清單解析
2 sum([i for i in range(100000000)])#記憶體占用大,機器容易卡死
3  
4 #生成器表達式
5 sum(i for i in range(100000000))#幾乎不占記憶體      

View Code

生成器相關的面試題

  生成器在程式設計中發生了很多的作用,善用生成器可以幫助我們解決很多複雜的問題

  除此之外,生成器也是面試題中的重點,在完成一些功能之外,人們也想出了很多魔性的面試題。

  面試題(1)

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

View Code

  面試題(2)

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

View Code

  tail&grep

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))
12 
13 複制代碼
14 複制代碼
15 
16 def add(n,i):
17     return n+i
18 
19 def test():
20     for i in range(4):
21         yield i
22 
23 g=test()
24 for n in [1,10]:
25     g=(add(n,i) for i in g)
26 
27 print(list(g))
28 
29 複制代碼
30 複制代碼
31 
32 import os
33 
34 def init(func):
35     def wrapper(*args,**kwargs):
36         g=func(*args,**kwargs)
37         next(g)
38         return g
39     return wrapper
40 
41 @init
42 def list_files(target):
43     while 1:
44         dir_to_search=yield
45         for top_dir,dir,files in os.walk(dir_to_search):
46             for file in files:
47                 target.send(os.path.join(top_dir,file))
48 @init
49 def opener(target):
50     while 1:
51         file=yield
52         fn=open(file)
53         target.send((file,fn))
54 @init
55 def cat(target):
56     while 1:
57         file,fn=yield
58         for line in fn:
59             target.send((file,line))
60 
61 @init
62 def grep(pattern,target):
63     while 1:
64         file,line=yield
65         if pattern in line:
66             target.send(file)
67 @init
68 def printer():
69     while 1:
70         file=yield
71         if file:
72             print(file)
73 
74 g=list_files(opener(cat(grep('python',printer()))))
75 
76 g.send('/test1')
77 
78 協程應用:grep -rl /dir      

View Code

轉載于:https://www.cnblogs.com/panfb/p/7811270.html