列表生成式,生成器和叠代器
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
錯誤,返回值包含在StopIteration
的value
中:
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
循環的數據類型有以下幾種:
一類是集合數據類型,如list
、tuple
、dict
、set
、str
等;
一類是generator
,包括生成器和帶yield
的generator function。
這些可以直接作用於for
循環的對象統稱為可叠代對象:Iterable
。
可以使用isinstance()
判斷一個對象是否是Iterable
對象:
而生成器不但可以作用於for
循環,還可以被next()
函數不斷調用並返回下一個值,直到最後拋出StopIteration
錯誤表示無法繼續返回下一個值了。
*可以被next()
函數調用並不斷返回下一個值的對象稱為叠代器:Iterator
。
可以使用isinstance()
判斷一個對象是否是Iterator
對象:
生成器都是Iterator
對象,但list
、dict
、str
雖然是Iterable
,卻不是Iterator
。
把list
、dict
、str
等Iterable
變成Iterator
可以使用iter()
函數:
你可能會問,為什麽list
、dict
、str
等數據類型不是Iterator
?
這是因為Python的Iterator
對象表示的是一個數據流,Iterator對象可以被next()
函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration
錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()
函數實現按需計算下一個數據,所以Iterator
的計算是惰性的,只有在需要返回下一個數據時它才會計算。
Iterator
甚至可以表示一個無限大的數據流,例如全體自然數。而使用list是永遠不可能存儲全體自然數的。
小結
凡是可作用於for
循環的對象都是Iterable
類型;
凡是可作用於next()
函數的對象都是Iterator
類型,它們表示一個惰性計算的序列;
集合數據類型如list
、dict
、str
等是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
列表生成式,生成器和叠代器