1. 程式人生 > >深入理解python的生成器表示式和列表解析

深入理解python的生成器表示式和列表解析

前言

      沒有用過的東西,沒有深刻理解的東西很難說自己會,而且被別人一問必然破綻百出。雖然之前有接觸過python協程的概念,但是隻是走馬觀花,這兩天的一次交談中,別人問到了協程,頓時語塞,死活想不起來曾經看過的東西,之後突然想到了yield,但為時已晚,只能說概念不清,所以本篇先縷縷python的生成器和yield關鍵字

什麼是生成器

  • 生成器是一個特殊的程式,可以被用作控制迴圈的迭代行為
  • 生成器類似於返回值為陣列的一個函式,這個函式可以接收引數,可以被呼叫,但是,不同於一般的函式會一次性返回包含了所有數值的陣列,生成器一次只產生一個值,這樣消耗的記憶體數量大大減少,而且允許呼叫函式可以很快的開始處理前幾個返回值。因此,生成器看起來像一個函式但是表現的卻像一個迭代器。

python中的生成器

python提供了兩種基本的方式。

  • 生成器函式:也是用def來定義,利用關鍵字yield一次返回一個結果,阻塞,重新開始
  • 生成器表示式:返回一個物件,這個物件只有在需要的時候才產生結果

下面詳細講解:

生成器函式

  • 為什麼叫生成器函式?因為他隨著時間的推移生成了一個數值佇列。一般的函式在執行完畢之後會返回一個值然後退出,但是生成器函式會自動掛起,然後重新拾起繼續執行,他會利用yield關鍵字關起函式,給呼叫者返回一個值,同時保留了當前的足夠多的狀態,可以使函式繼續執行。生成器和迭代協議是密切相關的,可迭代的物件都有一個__next()__成員方法,這個方法要麼返回迭代的下一項,要麼引起異常結束迭代

  • 為了支援迭代協議,擁有yield語句的函式被編譯為生成器,這類函式被呼叫時返回一個生成器物件,返回的物件支援迭代介面,即成員方法__next()__繼續從中斷處執行執行。
看下面的例子:
  1. # 計數器
  2. def create_counter(n):
  3. print("create counter")
  4. whileTrue:
  5. yield n
  6. print('increment n')
  7. n +=1
  8. cnt = create_counter(2)
  9. print(cnt)
  10. print(next(cnt))
  11. print(next(cnt))
  12. print(next(cnt))
  13. print(next(cnt))
  14. # D:\Python\python\python-3.6.1\Python36-64\python.exe
  15. # <generator object create_counter at 0x0000000002271D00>
  16. # create counter
  17. # 2
  18. # increment n
  19. # 3
  20. # increment n
  21. # 4
  22. # increment n
  23. # 5

分析一下這個例子:

  • 在create_counter函式中出現了關鍵字yield,預示著這個函式每次只產生一個結果值,這個函式返回一個生成器(通過第一行輸出可以看出來),用來產生連續的n值
  • 在創造生成器例項的時候,只需要像普通函式一樣呼叫就可以,但是這個呼叫卻不會執行這個函式,這個可以通過輸出看出來
  • next()函式將生成器物件作為自己的引數,在第一次呼叫的時候,他執行了create_counter()函式到yield語句,返回產生的值2
  • 我們重複的呼叫next()函式,每次他都會從上次被掛起的地方開始執行,直到再次遇到了yield關鍵字

為了更加深刻的理解,我們再舉一個例子。

  1. # 立方
  2. def cube(n):
  3. for i in range(n):
  4. yield i **3
  5. print(cube(5))
  6. print(next(cube(5)))
  7. print(next(cube(5)))
  8. print(next(cube(5)))
  9. print("for迴圈")
  10. for i in cube(5):
  11. print(i)
  12. # D:\Python\python\python-3.6.1\Python36-64\python.exe
  13. # <generator object cube at 0x0000000001FA1D00>
  14. # 0
  15. # 0
  16. # 0
  17. # for迴圈
  18. # 0
  19. # 1
  20. # 8
  21. # 27
  22. # 64

所以從理解函式的角度出發我們可以將yield類比為return,但是功能確實完全不同,在for迴圈中,會自動遵循迭代規則,每次呼叫next()函式,所以上面的結果不難理解。

生成器表示式:

生成器表示式來自於迭代和列表解析的組合,生成器表示式和列表解析類似,但是他使用圓括號而不是方括號括起來的。如下程式碼:

  1. # 列表解析生成列表
  2. list =[ x **3for x in range(5)]
  3. print(list)
  4. # [0, 1, 8, 27, 64]
  5. # 生成器表示式
  6. range_ =(x **3for x in range(5))
  7. print(range_)
  8. print(range_.__next__())
  9. print(range_.__next__())
  10. print(range_.__next__())
  11. # <generator object <genexpr> at 0x00000000024A1D00>
  12. # 0
  13. # 1
  14. # 8
  15. # 兩者之間轉換
  16. list1 = list(x **3for x in range(5))
  17. print(list1)
  18. # Traceback (most recent call last):
  19. # File "test.py", line 29, in <module>
  20. # list1 = list(x ** 3 for x in range(5))
  21. # TypeError: 'list' object is not callable

就操作而言,生成器表如果使用大量的next()函式會顯得十分不方便,for迴圈會自動出發next函式,所以可以按下面方式使用:

  1. for n in(x **3for x in range(5)):
  2. print('%s, %s'%(n, n * n))
  3. # 0, 0
  4. # 1, 1
  5. # 8, 64
  6. # 27, 729
  7. # 64, 4096
關於列表解析的概念和用法如下:

列表是一個很有用的資料結構,由於其靈活性在實際應用中被廣泛使用。對於列表來說,列表解析十分常用。


列表解析的語法如下,它迭代iterable中的每一個元素,當條件滿足的時候便根據表示式expr計算的內容生成一個元素並放入新的列表中,依次類推,最終返回整個列表。

  1. [expr for iter_item in iterableif cond_expr]

列表解析的使用非常靈活:

  • 支援多重巢狀,如果需要生成一個二維列表可以使用列表解析巢狀的方式

  1. nested_list =[['Hello','World'],['Goodbye','World']]
  2. print(nested_list)
  3. # [['Hello', 'World'], ['Goodbye', 'World']]
  4. nested_list =[[ele.upper()for ele in word]for word in nested_list]
  5. print(nested_list)
  6. # [['HELLO', 'WORLD'], ['GOODBYE', 'WORLD']]
  • 支援多重迭代

  1. b_ =[(a, b)for a in['1','2','3','4']for b in['a','b','c','d']if a != b]
  2. print(b_)
  3. # [('1', 'a'), ('1', 'b'), ('1', 'c'), ('1', 'd'), 
  4. # ('2', 'a'), ('2', 'b'), ('2', 'c'), ('2', 'd'), 
  5. # ('3', 'a'), ('3', 'b'), ('3', 'c'), ('3', 'd'), 
  6. # ('4', 'a'), ('4', 'b'), ('4', 'c'), ('4', 'd')]
  • 表示式可以是簡單表示式,也可以是複雜表示式,甚至是函式

  1. def f(v):
  2. if v %2==0:
  3. v = v **2
  4. else:
  5. v = v +1
  6. return v
  7. print([f(v)for v in[1,2,3,-1]if v >0])
  8. print([v **2if v %2==0else v +1for v in[1,2,3,-1]if v >0])
  9. # [2, 4, 4]
  10. # [2, 4, 4]
  • iterable可以是任意可迭代物件

  1. fp = open('wdf.py','r')
  2. res =[i for i in fp if'weixin'in i]
  3. print(res)

為什麼要推薦在需要生成列表的時候使用列表解析呢?

  • 使用列表解析更為直觀清晰,程式碼更為簡潔
  • 列表解析的效率更高,但是對於大資料處理,列表解析並不是一個最佳選擇,過多的記憶體消耗可能會導致MemoryError

除了列表可以使用列表解析的語法之外,其他內建的資料結構也支援,如下:

  1. #generator
  2. (expr for iter_item in iterable if cond_expr)
  3. #set
  4. {expr for iter_item in iterable if cond_expr}
  5. #dict
  6. {expr1: expr2 for iter_item in iterable if cond_expr}

兩者比較

        一個迭代既可以被寫成生成器函式,也可以被寫成生成器表示式,均支援自動和手動迭代。而且這些生成器只支援一個active迭代,也就是說生成器的迭代器就是生成器本身。

        絕大多數情況下,遍歷一個集合都是為了對元素應用某個動作或是進行篩選。你應該還記得有內建函式map和filter提供了這些功能,但Python仍然為這些操作提供了語言級的支援。

  1. (x+1for