1. 程式人生 > >列表生成式,生成器和叠代器

列表生成式,生成器和叠代器

number for 下一個 enum 繼續 調用函數 之前 順序執行 後拋

1.列表生成式

語法:[條件表達式 for i in iterable]

a=list(range(10))
b=[i+1 for i in a]   #這種形式就是列表生成式
print(b)

用列表生成式可以簡化代碼,等價於下面的幾種方法:

 1 #方法一
 2 a=list(range(10))
 3 b=[]
 4 for i in a:
 5     b.append(i+1)
 6 
 7 print(b)
 8 
 9 #方法二
10 a=list(range(10))
11 for index,i in enumerate(a):
12     a[index]+=1
13 print
(a) 14 15 #方法三 16 a=list(range(10)) 17 a=map(lambda a:a+1,a) #返回的是一個內存地址,想要調用需要用for循環 18 for i in a: 19 print(i)

2.生成器

通過列表生成式,我們可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素占用的空間都白白浪費了。

所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出後續的元素呢?這樣就不必創建完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator。

2.1 創建generator:

第一種方法很簡單,只要把一個列表生成式的[]改成(),就創建了一個generator:

1 l=[x*x for x in range(10)]
2 print(l)
3 g=(x*x for x in range(10))
4 print(g)

返回:

技術分享

生成器g返回的是函數的內存地址,想要打印出g裏面的元素,可以使用

for i in g:
    print(i)  

返回

技術分享

註意:

  1.創建l 和g的區別僅在於最外層的[](),l 是一個list,而g是一個generator。

  2.列表可以進行切片和索引,生成器g沒有辦法進行切片和索引。生成器只能在調用的時候才會返回相應的數據。

  3.打印生成器g的數據的方式,只有一個一個的取:一種是用for循環逐次打印,一種是用next()函數獲得generator的下一個返回值。

  生成器只記住當前的位置,既不知道之前的,也不知道之後的,只能一個一個地往後下一個取。

技術分享

我們講過,generator保存的是算法,每次調用next(g),就計算出g的下一個元素的值,直到計算到最後一個元素,沒有更多的元素時,拋出StopIteration的錯誤。

當然,上面這種不斷調用next(g)實在是太變態了,正確的方法是使用for循環,因為generator也是可叠代對象。

所以,我們創建了一個generator後,基本上永遠不會調用__next__(),而是通過for循環來叠代它,並且不需要關心StopIteration的錯誤。

generator非常強大。如果推算的算法比較復雜,用類似列表生成式的for循環無法實現的時候,還可以用函數來實現。

第二種方法:用函數做生成器,yield

實例:斐波拉契Fibonaccl數列:除第一個和第二個數外,任意一個數都可由前兩個數相加得到:

1,1,2,3,5,8,13,21,34,55....

斐波拉契數列用列表生成式寫不出來,但是,用函數把它打印出來卻很容易:

1 def fibo(max):
2     n,a,b=0,0,1
3     while n<max:
4         print(b)
5         a,b=b,a+b  #等價於t=(a,a+b),a=t[0],b=t[1] 但不必顯式寫出臨時變量t就可以賦值。
6         n+=1
7     return done
8 
9 fibo(10)

仔細觀察,可以看出,fib函數實際上是定義了斐波拉契數列的推算規則,可以從第一個元素開始,推算出後續任意的元素,這種邏輯其實非常類似generator。

也就是說,上面的函數和generator僅一步之遙。要把fib函數變成generator,只需要把print(b)改為yield b就可以了:

 1 def fibo(max):
 2     n,a,b=0,0,1
 3     while n<max:
 4         yield b
 5         a,b=b,a+b  #等價於t=(a,a+b),a=t[0],b=t[1] 但不必顯式寫出臨時變量t就可以賦值。
 6         n+=1
 7     return done
 8 
 9 
10 for i in fibo(10):
11     print(i)

這就是定義generator的另一種方法。如果一個函數定義中包含yield關鍵字,那麽這個函數就不再是一個普通函數,而是一個generator:

 f = fib(6)
 f
<generator object fib at 0x104feaaa0>

這裏,最難理解的就是generator和函數的執行流程不一樣。函數是順序執行,遇到return語句或者最後一行函數語句就返回。而變成generator的函數,在每次調用next()的時候執行,遇到yield語句返回,再次執行時從上次返回的yield語句處繼續執行。  

 1 def fibo(max):
 2     n,a,b=0,0,1
 3     while n<max:
 4         yield b
 5         a,b=b,a+b  #等價於t=(a,a+b),a=t[0],b=t[1] 但不必顯式寫出臨時變量t就可以賦值。
 6         n+=1
 7     return done‘  #異常時打印的消息
 8 
 9 
10 data = fibo(10)
11 print(data)
12 
13 print(data.__next__())
14 print(data.__next__())
15 print("幹點別的事")
16 print(data.__next__())
17 print(data.__next__())
18 print(data.__next__())
19 print(data.__next__())
20 print(data.__next__())
21 print(data.__next__())
22 print(data.__next__())
23 print(data.__next__())
24 print(data.__next__())
25 print(data.__next__())

返回:

技術分享

在上面fib的例子,我們在循環過程中不斷調用yield,就會不斷中斷。當然要給循環設置一個條件來退出循環,不然就會產生一個無限數列出來。

同樣的,把函數改成generator後,我們基本上從來不會用next()來獲取下一個返回值,而是直接使用for循環來叠代。但是用for循環調用generator時,發現拿不到generator的return語句的返回值。如果想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIterationvalue

 1 def fibo(max):
 2     n,a,b=0,0,1
 3     while n<max:
 4         yield b   #想要返回什麽,就在哪裏加yield。
 5         a,b=b,a+b  #等價於t=(a,a+b),a=t[0],b=t[1] 但不必顯式寫出臨時變量t就可以賦值。
 6         n+=1
 7     return done
 8 
 9 f=fibo(10)
10 while True:
11     try:
12         x=next(f)   #debugger中,x=next(f)就是調用f,到yield b中止,下次調用函數,則繼續運行a,b=a,a+b;n+=1
13         print(fibo:,x)
14     except StopIteration as e:
15         print(Generator return value:,e.value)
16         break

2.2 通過yield在單線程情況下實現並發運算的效果

 1 #典型的生產者-消費者模型
 2 import time
 3 def consumer(name):
 4     print(%s 準備吃包子啦!%name)
 5     while True:
 6         baozi=yield   #通過下面的send給yield傳值,baozi=c.send(‘object‘)
 7 
 8         print(包子[%s]來了,被[%s]吃了!%(baozi,name))
 9 
10 
11 def producer(name):
12     c0=consumer(name)
13     c1=consumer(Zzz)
14     c0.__next__()  #只有調用__next__()才能從開始調用consumer()函數
15     c1.__next__()
16     print(廚師開始做包子)
17     for i in range(10):
18         time.sleep(1)
19         print(做了2個包子!)
20         c0.send(i)
21         c1.send(i)
22 
23 
24 producer(david)

返回:

技術分享

3.叠代器

我們已經知道,可以直接作用於for循環的數據類型有以下幾種:

  一類是集合數據類型,如listtupledictsetstr等;

  一類是generator,包括生成器和帶yield的generator function。

這些可以直接作用於for循環的對象統稱為可叠代對象:Iterable

可以使用isinstance()判斷一個對象是否是Iterable對象:

技術分享

而生成器不但可以作用於for循環,還可以被next()函數不斷調用並返回下一個值,直到最後拋出StopIteration錯誤表示無法繼續返回下一個值了。

*可以被next()函數調用並不斷返回下一個值的對象稱為叠代器:Iterator

可以使用isinstance()判斷一個對象是否是Iterator對象:

技術分享

生成器都是Iterator對象,但listdictstr雖然是Iterable,卻不是Iterator

listdictstrIterable變成Iterator可以使用iter()函數:

技術分享

你可能會問,為什麽listdictstr等數據類型不是Iterator

這是因為Python的Iterator對象表示的是一個數據流,Iterator對象可以被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函數實現按需計算下一個數據,所以Iterator的計算是惰性的,只有在需要返回下一個數據時它才會計算。

Iterator甚至可以表示一個無限大的數據流,例如全體自然數。而使用list是永遠不可能存儲全體自然數的。

小結

凡是可作用於for循環的對象都是Iterable類型;

凡是可作用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列;

集合數據類型如listdictstr等是Iterable但不是Iterator,不過可以通過iter()函數獲得一個Iterator對象。

Python的for循環本質上就是通過不斷調用next()函數實現的,例如:

1 2 for x in [1, 2, 3, 4, 5]: pass

實際上完全等價於:

技術分享
# 首先獲得Iterator對象:
it = iter([1, 2, 3, 4, 5])
# 循環:
while True:
    try:
        # 獲得下一個值:
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循環
        break
技術分享

列表生成式,生成器和叠代器