如今有個需求,現有列表a=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
,要求你把列表裏的每一個值加1,你怎麼實現?python
一、二逼青年版算法
生成一個新列表b,遍歷列表a,把每一個值加1後存在b裏,最後再把a=b, 這樣二逼的緣由不言而喻,生成了新列表,浪費了內存空間。編程
>>> a [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> b = [] >>> for i in a:b.append(i+1) ... >>> b [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> a = b >>> a [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
二、普通青年版併發
a = [1,3,4,6,7,7,8,9,11] for index,i in enumerate(a): a[index] +=1 print(a)
三、略屌青年版app
>>> a [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> a = map(lambda x:x+1, a) >>> a >>> for i in a:print(i) ... 3 5 7 9 11
四、裝逼青年版函數
>>> a = [i+1 for i in range(10)] >>> a [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
這樣的寫法就叫作列表生成式,有什麼用呢?裝逼用,哈哈,寫出來顯的高級,效果跟上面的都同樣。code
經過列表生成式,咱們能夠直接建立一個列表。可是,受到內存限制,列表容量確定是有限的。並且,建立一個包含100萬個元素的列表,不只佔用很大的存儲空間,若是咱們僅僅須要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。好比我要循環100萬次,按py的語法,for i in range(1000000)會先生成100萬個值的列表。可是循環到第50次時,我就不想繼續了,就退出了。可是90多萬的列表元素就白爲你提早生成了。對象
for i in range(1000000): if i == 50: break print(i)
因此,若是列表元素能夠按照某種算法推算出來,那咱們是否能夠在循環的過程當中不斷推算出後續的元素呢?blog
像上面這個循環,每次循環只是+1而已,咱們徹底能夠寫一個算法,讓他執行一次就自動+1,這樣就沒必要建立完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算後面元素的機制,稱爲生成器:generator。內存
要建立一個generator,有不少種方法。第一種方法很簡單,只要把一個列表生成式的[]
改爲(),
就建立了一個generator:
>>> [x * x for x in range(10)] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> >>> (x * x for x in range(10)) at 0x101ebc3b8>
(x*x for x in range(10))生成的就是一個生成器。
咱們能夠直接打印出list的每個元素,但咱們怎麼打印出generator的每個元素呢?
若是要一個一個打印出來,能夠經過next()
函數得到generator的下一個返回值:
>>> g = (x * x for x in range(10)) >>> next(g) 0 >>> next(g) 1 >>> next(g) 4 >>> next(g) 9 >>> next(g) 16 >>> next(g) 25 >>> next(g) 36 >>> next(g) 49 >>> next(g) 64 >>> next(g) 81 >>> next(g) Traceback (most recent call last): File "", line 1, in StopIteration
咱們講過,generator保存的是算法,每次調用next(g)
就計算出g
的下一個元素的值,直到計算到最後一個元素,沒有更多的元素時,拋出StopIteration
的錯誤。
固然,上面這種不斷調用next(g)
實在是太變態了,正確的方法是使用for
循環,由於generator也是可迭代(遍歷)對象:
>>> g = (x * x for x in range(10)) >>> for n in g: ... print(n) ... 0 1 4 9 16 25 36 49 64 81
經過for循環來迭代它,就不須要關心StopIteration的錯誤了。
一、函數生成器
generator很是強大。若是推算的算法比較複雜,用相似列表生成式的for循環沒法實現的時候,還能夠用函數來實現。
好比,著名的斐波拉契數列(Fibonacci),除第一個和第二個數外,任意一個數均可由前兩個數相加獲得:
1, 1, 2, 3, 5, 8, 13, 21, 34, …
實現100之內的斐波那契數代碼:
a,b = 0,1 n = 0 # 斐波那契數 while n < 100: n = a + b a = b # 把b的舊值給到a b = n # 新的b = a + b(舊b的值) print(n)
改爲函數也能夠的
def fib(max): a,b = 0,1 n = 0 # 斐波那契數 while n < max: n = a + b a = b # 把b的舊值給到a b = n # 新的b = a + b(舊b的值) print(n) fib(100)
輸出:
1 2 3 5 8 13 21 34 55 89 144
仔細觀察,能夠看出,fib
函數其實是定義了斐波拉契數列的推算規則,能夠從第一個元素開始,推算出後續任意的元素,這種邏輯其實很是相似generator。
也就是說,上面的函數和generator僅一步之遙。要把fib函數變成generator,只須要把print(n)改成yield n就能夠了:
def fib(max): a,b = 0,1 n = 0 # 斐波那契數 while n < max: n = a + b a = b # 把b的舊值給到a b = n # 新的b = a + b(舊b的值) #print(n) yield n # 程序走到這,就會暫停下來,返回n到函數外面,直到被next方法調用時喚醒 f = fib(100) # 注意這句調用時,函數並不會執行,只有下一次調用next時,函數纔會真正執行 print(f) print(f.__next__()) print(f.__next__()) print(f.__next__()) print(f.__next__())
輸出:
1 2 3 5
這就是定義generator的另外一種方法。若是一個函數定義中包含yield
關鍵字,那麼這個函數就再也不是一個普通函數,而是一個generator:
這裏,最難理解的就是generator和函數的執行流程不同。函數是順序執行,遇到return語句或者最後一行函數語句就返回。而變成generator的函數,在每次調用next()的時候執行,遇到yield語句暫停並返回數據到函數外,再次被next()調用時從上次返回的yield語句處繼續執行。
在上面fib的例子,咱們在循環過程當中不斷調用yield
,函數就會不斷的中斷(暫停)。固然要給循環設置一個條件來退出循環,否則就會產生一個無限數列出來。一樣的,把函數改爲generator後,咱們基本上歷來不會用next()
來獲取下一個返回值,而是直接使用for
循環來迭代:
f = fib(100) # 注意這句調用時,函數並不會執行,只有下一次調用next時,函數纔會真正執行 for i in f: print(i) #輸出: 1 2 3 ... ... 55 89 144
二、併發編程
雖然咱們還沒學併發編程,但咱們確定聽過cpu 多少核多少核之類的,cpu的多核就是爲了能夠實現並行運算,讓你同時邊聽歌、邊聊qq、邊刷知乎。單核的cpu同一時間只能幹一個事,因此你用單核電腦同時作好幾件事的話,就會變的很慢,由於cpu要在不一樣程序任務間來回切換。
經過yield, 咱們能夠實現單核下併發作多件事的效果。
import time def consumer(name): print("%s 準備吃包子啦!" %name) while True: baozi = yield # yield能夠接收到外部send傳過來的數據並賦值給baozi print("包子[%s]來了,被[%s]吃了!" %(baozi,name)) c = consumer('A') c2 = consumer('B') c.__next__() # 執行一下next可使上面的函數走到yield那句。 這樣後面的send語法才能生效 c2.__next__() print("----老子開始準備作包子啦!----") for i in range(10): time.sleep(1) print("作了2個包子!") c.send(i) # send的做用=next, 同時還把數據傳給了上面函數裏的yield c2.send(i)
注意:調用send(x)給生成器傳值時,必須確保生成器已經執行過一次next()調用, 這樣會讓程序走到yield位置等待外部第2次調用。