Python函數編程——列表生成式和生成器

Python函數編程——列表生成式和生成器

1、列表生成式

如今有個需求,現有列表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

2、生成器generator

經過列表生成式,咱們能夠直接建立一個列表。可是,受到內存限制,列表容量確定是有限的。並且,建立一個包含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次調用。

相關文章
相關標籤/搜索