1. 程式人生 > >知識點:從迭代器一直到yield from

知識點:從迭代器一直到yield from

最近在跟一個系列,

難度和篇幅比較合適我這樣的懶人。

敲下程式碼,作下注釋,看看輸出,就蠻好。

https://www.cnblogs.com/wongbingming/p/9095243.html

import collections
from collections.abc import Iterable, Iterator, Generator
from inspect import getgeneratorstate

"""
藉助collections.abc這個模組,
使用isinstance()來類別一個物件是否是可迭代的(Iterable),
是否是迭代器(Iterator),
是否是生成器(Generator)。
""" astr = "XiaoMing" print("String: {}".format(astr)) print(isinstance(astr, Iterable)) print(isinstance(astr, Iterator)) print(isinstance(astr, Generator)) # String: XiaoMing # True # False # False """ 從結果來看,這些可迭代物件都不是迭代器,也不是生成器。 它們有一個共同點,就是它們都可以使用for來迴圈。 """ alist = [21, 45, 65, 78]
print("List: {}".format(alist)) print(isinstance(alist, Iterable)) print(isinstance(alist, Iterator)) print(isinstance(alist, Generator)) # List: [21, 45, 65, 78] # True # False # False adict = {'name': "xiaoming", 'age': 18} print("Dict: {}".format(adict)) print(isinstance(adict, Iterable))
print(isinstance(adict, Iterator)) print(isinstance(adict, Generator)) # Dict: {'name': 'xiaoming', 'age': 18} # True # False # False """ 可迭代物件,是其內部實現了,__iter__ 這個魔術方法。 可以通過,dir()方法來檢視是否有__iter__來判斷一個變數是否是可迭代的。 """ adeque = collections.deque('abcdefg') print("deque: {}".format(adeque)) print(isinstance(adeque, Iterable)) print(isinstance(adeque, Iterator)) print(isinstance(adeque, Generator)) # deque: deque(['a', 'b', 'c', 'd', 'e', 'f', 'g']) # True # False # False """ 對比可迭代物件,迭代器其實就只是多了一個函式而已。 就是__next__(),我們可以不再使用for迴圈來間斷獲取元素值。 而可以直接使用next()方法來實現。 可以通過,dir()方法來檢視是否有__next__來判斷一個變數是否是迭代器的 """ aIterator = iter(astr) print("aIterator: {}".format(aIterator)) print(isinstance(aIterator, Iterable)) print(isinstance(aIterator, Iterator)) print(isinstance(aIterator, Generator)) # aIterator: <str_iterator object at 0x00000000023F5358> # True # True # False """ 而生成器,則是在迭代器的基礎上(可以用for迴圈,可以使用next()),再實現了yield。 yield 是什麼東西呢,它相當於我們函式裡的return。 在每次next(),或者for遍歷的時候, 都會yield這裡將新的值返回回去,並在這裡阻塞,等待下一次的呼叫。 正是由於這個機制,才使用生成器在Python程式設計中大放異彩。 實現節省記憶體,實現非同步程式設計。 可迭代物件和迭代器,是將所有的值都生成存放在記憶體中, 而生成器則是需要元素才臨時生成,節省時間,節省空間。 """ aGenerator = (x*x for x in range(10)) print("aGenerator: {}".format(aGenerator)) print(isinstance(aGenerator, Iterable)) print(isinstance(aGenerator, Iterator)) print(isinstance(aGenerator, Generator)) # aGenerator: <generator object <genexpr> at 0x0000000002727FC0> # True # True # True """ 由於生成器並不是一次生成所有元素,而是一次一次的執行返回, 那麼如何刺激生成器執行(或者說啟用)呢? 啟用主要有兩個方法 使用next() 使用generator.send(None) 通過交替執行,說明這兩種方法是等價的。 """ gen = aGenerator print(gen.send(None)) print(next(gen)) print(gen.send(None)) print(next(gen)) print(gen.send(None)) print(next(gen)) # 0 # 1 # 4 # 9 # 16 # 25 """ 生成器在其生命週期中,會有如下四個狀態 GEN_CREATED # 等待開始執行 GEN_RUNNING # 直譯器正在執行(只有在多執行緒應用中才能看到這個狀態) GEN_SUSPENDED # 在yield表示式處暫停 GEN_CLOSED # 執行結束 在生成器工作過程中, 若生成器不滿足生成元素的條件, 就會/應該 丟擲異常(StopIteration)。 我們在自己定義一個生成器的時候, 我們也應該在不滿足生成元素條件的時候,丟擲異常。 """ def mygen(n): now = 0 while now < n: yield now now += 1 # 丟擲異常 raise StopIteration gen2 = mygen(2) print(getgeneratorstate(gen2)) print(next(gen2)) print(getgeneratorstate(gen2)) print(next(gen2)) gen2.close() print(getgeneratorstate(gen2)) # GEN_CREATED # 0 # GEN_SUSPENDED # 1 # GEN_CLOSED """ 協程和執行緒,有相似點,多個協程之間和執行緒一樣,只會交叉序列執行; 也有不同點,執行緒之間要頻繁進行切換,加鎖,解鎖, 從複雜度和效率來看,和協程相比,這確是一個痛點。 協程通過使用 yield 暫停生成器, 可以將程式的執行流程交給其他的子程式, 從而實現不同子程式的之間的交替執行。 """ def jumping_range(N): index = 0 while index < N: # yield index 是將index return給外部呼叫程式。 # jump = yield 可以接收外部程式通過send()傳送的資訊,並賦值給jump jump = yield index if jump is None: jump = 1 index += jump # 丟擲異常 raise StopIteration itr = jumping_range(5) print(next(itr)) print(itr.send(2)) print(itr.send(-1)) print(itr.send(None)) print(next(itr)) print(itr.send(-2)) print(next(itr)) # 0 # 2 # 1 # 2 # 3 # 1 # 2 """ yield from後面加上可迭代物件, 他可以把可迭代物件裡的每個元素一個一個的yield出來, 對比yield來說程式碼更加簡潔,結構更加清晰。 """ def gen3(*args, **kwargs): for item in args: yield from item new_list = gen3(astr, alist, adict, aGenerator) print(list(new_list)) # ['X', 'i', 'a', 'o', 'M', 'i', 'n', 'g', 21, 45, 65, 78, 'name', 'age', 36, 49, 64, 81] # 子生成器 def average_gen(): total = 0 count = 0 average = 0 while True: # 子生成器yield的值,直接返回給呼叫方。 new_num = yield average if new_num is None: break count += 1 total += new_num average = total/count # 每一次return,都意味著當前協程結束。 return total, count, average # 委託生成器 # 作用是:在呼叫方與子生成器之間建立一個雙向通道。 def proxy_gen(): while True: # 只有子生成器要結束(return)了, # yield from左邊的變數才會被賦值,後面的程式碼才會執行。 total, count, average = yield from average_gen() print("計算完畢!!\n總共傳入 {} 個數值, 總和:{},平均數:{}" .format(count, total, average)) # 呼叫方 def main_gen(): calc_average = proxy_gen() next(calc_average) # 呼叫方可以通過send()直接傳送訊息給子生成器 print(calc_average.send(10)) print(calc_average.send(20)) print(calc_average.send(30)) calc_average.send(None) # 結束協程 # 如果此處再呼叫calc_average.send(10),由於上一協程已經結束,將重開一協程 main_gen() # 10.0 # 15.0 # 20.0 # 計算完畢!! # 總共傳入 3 個數值, 總和:60,平均數:20.0