Python自動化開發學習4-2
列表生成式
先看2段代碼
a = [ i*2 for i in range(10) ]
print(a)
#
b = []
for i in range(10):
b.append(i*2)
print(b)
a和b的效果一樣,但是a使用的代碼更加簡潔
列表生成式也可以使用函數,生成更加復雜的列表
a = [ max(i,6) for i in range(10) ] print(a)
上面的是鋪墊,主要講下面的生成器
生成器
用列表生成式,我們可以直接創建一個列表。等列表創建完之後,我們可以訪問列表中的元素。但是這都得等列表生成完之後。如果列表很大很復雜,就需要耗費很長的時間和內存空間,然後我們才能訪問列表中的元素。
如果列表元素可以按照某種算法推算出來,我們不必創建完列表在進行後續的操作,而是一邊循環一邊引用每一個新創建的元素。在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator。
a = [ i*2 for i in range(10) ] for i in a: print(i) b = ( i*2 for i in range(10) ) # 這個是生成器 for i in b: print(i) print(type(a),type(b))
上面的代碼中,a和b的效果是一樣的。但是a的機制是先生成完整列表,再執行for循環。而b中只是記錄了一個算法,並沒有生成任何數據。等到for循環調用b的時候,才一邊循環一邊計算每一個元素。
這裏我們感覺不出兩種機制的差別,但是當列表很大或者計算很復雜的情況下,就能發現兩者的差別。我們強行來增加生成列表的時間。
import time def f(n): time.sleep(1) return n*2 a = [ f(i) for i in range(10) ] for i in a: print(i) b = ( f(i) for i in range(10) ) # 這個是生成器 for i in b: print(i)
這下就發現區別了,a是等待了很長時間來生成列表,然後快速的把結果輸出。而b是計算一個元素,輸出一個元素,開始沒有很長的等待時間,但是每次輸出之間是要等待1秒來生成新的元素。
順便我看了一下兩者的效率,理論上是差不多的,但是測試下來a要耗時10.02秒,b要耗時10.005秒。每次結果會不同,但是都在這個數字級別。
import time def f(n): time.sleep(1) return n*2 t = time.time() a = [ f(i) for i in range(10) ] for i in a: print(i) print(time.time()-t) t = time.time() b = ( f(i) for i in range(10) ) # 這個是生成器 for i in b: print(i) print(time.time()-t)
看來使用生成器,也是一個更加效率的方法。
生成器是一邊循環一邊計算的,所有的元素需要一個一個計算出來。只能一個一個的計算出來,並且只記住了當前的位置,在當前位置你只能取到下一個值,不能退回去,也不能跳過。
所以,生成器只有一個方法.__next__
b = ( i*2 for i in range(10) ) print(b.__next__()) print(b.__next__()) print(b.__next__()) print(b.__next__()) print(b.__next__()) print(b.__next__()) print(b.__next__()) print(b.__next__()) # 超出範圍會報錯
上面好LOW,一般都用循環,__next__用的不是很多。
在繼續之前,先用函數寫一個斐波那契數列。
斐波那契數列指的是這樣一個數列 1, 1, 2, 3, 5, 8, 13, 21,這個數列從第3項開始,每一項都等於前兩項之和。
def fib(n): i,a,b = 0,0,1 while i < n: print(b) a,b = b,a+b i += 1 return "結束" fib(10)
把上面的函數的print(b)替換成yield b,就實現了用函數做了一個生成器。
def fib(n): i,a,b = 0,0,1 while i < n: #print(b) yield b # 替換成這句 a,b = b,a+b i += 1 return "結束" f = fib(10) print(type(f)) # f的數據類型是generator
這是定義生成器的另一種方法。如果一個函數定義中包含yield
關鍵字,那麽這個函數就不再是一個普通函數,而是一個生成器。
生成器在語句執行遇到yield的時候會返回,但是會記住當前的位置,如果你使用.__next__()則會在之前的位置繼續執行。也就是生成器在執行並且返回之後,並沒有完全結束。跳出生成器後可以正常執行別的語句,在需要的時候再從之前生成器返回的位置繼續執行下一次循環。這樣的話生成器最後的return就沒有意義了。接著看,我們先試著打印出生成器中的所有元素。
def fib(n): i,a,b = 0,0,1 while i < n: #print(b) yield b a,b = b,a+b i += 1 return "結束" f = fib(10) print(type(f)) print(f.__next__()) print("你可以隨時插入你的語句") print(f.__next__()) print("生成器會記住之前的位置繼續循環") print(f.__next__()) print(f.__next__()) print(f.__next__()) print(f.__next__()) print(f.__next__()) print(f.__next__()) print(f.__next__()) print("打了那麽多就是要超出限制") print(f.__next__()) print(f.__next__())
如果超出了繼續取,就會報錯。仔細看一下報錯的內容,最後的StopIteration:後的內容就是你return的內容。我們可以用try來捕獲這個錯誤從而獲取return的值。try還沒講到,後面應該會細講。
def fib(n): i,a,b = 0,0,1 while i < n: #print(b) yield b a,b = b,a+b i += 1 return "結束" f = fib(10) while 1: try: x = next(f) # x = f.__next__() print(x) except StopIteration as e: print("返回值是:",e.value) break
這裏x=next(f)和x=f,__next__()效果一樣。這裏上課沒講,暫時沒發現有什麽區別。
通過yield還可以實現單線程下的並行效果,這個不叫並行,叫協程。這裏好煩,直接抄老師的例子了。
import time def consumer(name): print("%s 準備吃包子啦!" %name) while True: baozi = yield # 這裏中斷,通過send傳值進來繼續執行 print("包子[%s]來了,被[%s]吃了!" %(baozi,name)) def producer(name): c = consumer(‘A‘) # 定義了一個consumer(‘A‘),但是並沒有運行 c2 = consumer(‘B‘) # 定義了一個consumer(‘B‘) c.__next__() # consumer(‘A‘)啟動運行,運行到yield之前,打印"準備吃包子啦" c2.__next__() # consumer(‘B‘)啟動運行 print("老子開始準備做包子啦!") for i in range(10): time.sleep(1) print("做了2個包子!") c.send(i) # send是給yield傳值 c2.send(i) producer("C")
上面的例子裏.send(i)之前沒提過,和.__next__()一樣,但是sent可以傳一個值回去。
上面的例子就是producer啟動以後,又啟動了2個consumer。consumer運行到yield中斷,等待。producer將值通過send傳給consumer後,consumer就執行一次循環,然後再中斷,等待新的值傳入。
叠代器
可直接作用於for循環的對象,統稱為可叠代對象:Iterable。
使用下面這個方法可以判斷一個對象是否叠代
from collections import Iterable print(isinstance(‘‘,Iterable)) # 字符串可叠代 print(isinstance(123,Iterable)) # 數字不可叠代 print(isinstance((),Iterable)) print(isinstance([],Iterable)) print(isinstance({},Iterable)) print(isinstance((x for x in range(10)),Iterable)) # 這個是生成器,可叠代
生成器不但可以作用於for循環,還可以next,不斷調用返回下一個值。
可以被__next__()
調用並不斷返回下一個值的對象稱為叠代器:Iterator。
使用下面的方法再判斷一下之前的對象是否是叠代器
from collections import Iterator print(isinstance(‘‘,Iterator)) print(isinstance(123,Iterator)) print(isinstance((),Iterator)) print(isinstance([],Iterator)) print(isinstance({},Iterator)) print(isinstance((x for x in range(10)),Iterator))
只有最後一個可以__next__(),只有最後一個是True,是叠代器。
上面的這些可叠代對象,目前都不是叠代器。通過iter()就可以把這些可叠代對象變成叠代器。
from collections import Iterator a = [1,2,3,4,5,6] print(isinstance(a,Iterator)) b = iter(a) print(isinstance(b,Iterator)) print(b.__next__()) print(b.__next__()) print(b.__next__())
Python的Iterator對象表示的是一個數據流,Iterator對象可以被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函數實現按需計算下一個數據,所以Iterator的計算是惰性的,只有在需要返回下一個數據時它才會計算。這樣,叠代器甚至可以表示一個無限大的數據流。
我們的for循環,本質上就是通過不斷調用next來實現的。
Python自動化開發學習4-2