小白學 Python(21):生成器基礎

  • 2019 年 11 月 14 日
  • 筆記

人生苦短,我選Python

前文傳送門

小白學 Python(1):開篇

小白學 Python(2):基礎數據類型(上)

小白學 Python(3):基礎數據類型(下)

小白學 Python(4):變數基礎操作

小白學 Python(5):基礎運算符(上)

小白學 Python(6):基礎運算符(下)

小白學 Python(7):基礎流程式控制制(上)

小白學 Python(8):基礎流程式控制制(下)

小白學 Python(9):基礎數據結構(列表)(上)

小白學 Python(10):基礎數據結構(列表)(下)

小白學 Python(11):基礎數據結構(元組)

小白學 Python(12):基礎數據結構(字典)(上)

小白學 Python(13):基礎數據結構(字典)(下)

小白學 Python(14):基礎數據結構(集合)(上)

小白學 Python(15):基礎數據結構(集合)(下)

小白學 Python(16):基礎數據類型(函數)(上)

小白學 Python(17):基礎數據類型(函數)(下)

小白學 Python(18):基礎文件操作

小白學 Python(18):基礎文件操作

小白學 Python(19):基礎異常處理

小白學 Python(20):迭代器基礎

生成器

我們前面聊過了為什麼要使用迭代器,各位同學應該還有印象吧(說沒有的就太過分了)。

列表太大的話會佔用過大的記憶體,可以使用迭代器,只拿出需要使用的部分。

生成器的設計原則和迭代器是相似的,如果需要一個非常大的集合,不會將元素全部都放在這個集合中,而是將元素保存成生成器的狀態,每次迭代的時候返回一個值。

比如我們要生成一個列表,可以採用如下方式:

list1 = [x*x for x in range(10)]  print(list1)

結果如下:

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

如果我們生成的列表非常的巨大,比如:

list2 = [x*x for x in range(1000000000000000000000000)]

結果如下:

Traceback (most recent call last):    File "D:/Development/Projects/python-learning/base-generator/Demo.py", line 3, in <module>      list2 = [x*x for x in range(1000000000000000000000000)]    File "D:/Development/Projects/python-learning/base-generator/Demo.py", line 3, in <listcomp>      list2 = [x*x for x in range(1000000000000000000000000)]  MemoryError

報錯了,報錯資訊提示我們存儲異常,並且整個程式運行了相當長一段時間。友情提醒,這麼大的列表創建請慎重,如果電腦配置不夠很有可能會將電腦卡死。

如果我們使用生成器就會非常方便了,而且執行速度嗖嗖的。

generator1 = (x*x for x in range(1000000000000000000000000))  print(generator1)  print(type(generator1))

結果如下:

<generator object <genexpr> at 0x0000014383E85B48>  <class 'generator'>

那麼,我們使用了生成器以後,怎麼讀取生成器生成的數據呢?

當然是和之前的迭代器一樣的拉,使用 next() 函數:

generator2 = (x*x for x in range(3))  print(next(generator2))  print(next(generator2))  print(next(generator2))  print(next(generator2))

結果如下:

Traceback (most recent call last):    File "D:/Development/Projects/python-learning/base-generator/Demo.py", line 14, in <module>      print(next(generator2))  StopIteration

直到最後,拋出 StopIteration 異常。

但是,這種使用方法我們並不知道什麼時候會迭代結束,所以我們可以使用 for 循環來獲取每生成器生成的具體的元素,並且使用 for 循環同時也無需關心最後的 StopIteration 異常。

generator3 = (x*x for x in range(5))  for index in generator3:      print(index)

結果如下:

0  1  4  9  16

generator 非常的強大,本質上, generator 並不會取存儲我們的具體元素,它存儲是推算的演算法,通過演算法來推算出下一個值。

如果推算的演算法比較複雜,用類似列表生成式的 for 循環無法實現的時候,還可以用函數來實現。

比如我們定義一個函數,emmmmmm,還是簡單點吧,大家領會精神:

def print_a(max):      i = 0      while i < max:          i += 1          yield i    a = print_a(10)  print(a)  print(type(a))

結果如下:

<generator object print_a at 0x00000278C6AA5CC8>  <class 'generator'>

這裡使用到了關鍵字 yield , yield 和 return 非常的相似,都可以返回值,但是不同的是 yield 不會結束函數。

我們調用幾次這個用函數創建的生成器:

print(next(a))  print(next(a))  print(next(a))  print(next(a))

結果如下:

1  2  3  4

可以看到,當我們使用 next() 對生成器進行一次操作的時候,會返回一次循環的值,在 yield 這裡結束本次的運行。但是在下一次執行 next() 的時候,會接著上次的斷點接著運行。直到下一個 yield ,並且不停的循環往複,直到運行至生成器的最後。

還有一種與 next() 等價的方式,直接看示例程式碼吧:

print(a.__next__())  print(a.__next__())

結果如下:

5  6

接下來要介紹的這個方法就更厲害了,不僅能迭代,還能給函數再傳一個值回去:

def print_b(max):      i = 0      while i < max:          i += 1          args = yield i          print('傳入參數為:' + args)    b = print_b(20)  print(next(b))  print(b.send('Python'))

結果如下:

1  傳入參數為:Python  2

上面講了這麼多,可能各位還沒想到生成器能有什麼具體的作用吧,這裡我來提一個——協程。

在介紹什麼是協程之前先介紹下什麼是多執行緒,就是在同一個時間內可以執行多個程式,簡單理解就是你平時可能很經常的一邊玩手機一邊聽音樂(毫無違和感)。

協程更貼切的解釋是流水線,比如某件事情必須 A 先做一步, B 再做一步,並且這兩件事情看起來要是同時進行的。

def print_c():      while True:          print('執行 A ')          yield None  def print_d():      while True:          print('執行 B ')          yield None    c = print_c()  d = print_d()  while True:      c.__next__()      d.__next__()

結果如下:

...  執行 A  執行 B  執行 A  執行 B  執行 A  執行 B  執行 A  執行 B  執行 A  執行 B  ...

因為 while 條件設置的是永真,所以這個循環是不會停下來的。

這裡我們定義了兩個生成器,並且在一個循環中往複的調用這兩個生成器,這樣看起來就是兩個任務在同時執行。

最後的協程可能理解起來稍有難度,有問題可以在公眾號後台問我哦~~~

示例程式碼

本系列的所有程式碼小編都會放在程式碼管理倉庫 Github 和 Gitee 上,方便大家取用。

示例程式碼-Github

示例程式碼-Gitee

Tags: