導讀:此文由伯樂線上 –劉志軍編譯自stackoverflow Python標籤中投票率最高的一個問題《The Python yield keyword explained》,e-satis 詳細回答了關於yield 以及 generator、iterable、iterator、iteration之間的關係。
迭代器(Iterator)
為了理解yield是什麼,首先要明白生成器(generator)是什麼,在講生成器之前先說說迭代器(iterator),當建立一個列表(list)時,你可以逐個的讀取每一項,這就叫做迭代(iteration)。
1 2 3 4 5 6 |
mylist = [1, 2, 3] for i in mylist : print(i) 1 2 3 |
1 2 3 4 5 6 |
mylist = [x*x for x in range(3)] for i in mylist : print(i) 0 1 4 |
你可以使用“for··· in ···”來操作可迭代物件,如:list,string,files,這些迭代物件非常方便我們使用,因為你可以按照你的意願進行重複的讀取。但是你不得不預先儲存所有的元素在記憶體中,那些物件裡有很多元素時,並不是每一項都對你有用。
生成器(Generators)
生成器同樣是可迭代物件,但是你只能讀取一次,因為它並沒有把所有值存放記憶體中,它動態的生成值:
1 2 3 4 5 6 |
mygenerator = (x*x for x in range(3)) for i in mygenerator : print(i) 0 1 4 |
使用()和[]結果是一樣的,但是,第二次執行“ for in mygenerator”不會有任何結果返回,因為它只能使用一次。首先計算0,然後計算1,之後計算4,依次類推。
Yield
Yield是關鍵字, 用起來像return,yield在告訴程式,要求函式返回一個生成器。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def createGenerator() : mylist = range(3) for i in mylist : yield i*i mygenerator = createGenerator() # create a generator print(mygenerator) # mygenerator is an object! <generator object createGenerator at 0xb7555c34> for i in mygenerator: print(i) 0 1 4 |
這個示例本身沒什麼意義,但是它很清晰地說明函式將返回一組僅能讀一次的值,要想掌握yield,首先必須理解的是:當你呼叫生成器函式的時候,如上例中的createGenerator(),程式並不會執行函式體內的程式碼,它僅僅只是返回生成器物件,這種方式頗為微妙。函式體內的程式碼只有直到每次迴圈迭代(for)生成器的時候才會執行。
函式第一次執行時,它會從函式開始處直到碰到yield時,就返回迴圈的第一個值,然後,互動的執行、返回,直到沒有值返回為止。如果函式在執行但是並沒有遇到yield,就認為該生成器是空,原因可能是迴圈終止,或者沒有滿足任何”if/else”。
接下來讀一小段程式碼來理解生成器的優點:
控制生成器窮舉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
>>> class Bank(): # 建立銀行,構造ATM機 ... crisis = False ... def create_atm(self) : ... while not self.crisis : ... yield "$100" >>> hsbc = Bank() # 沒有危機時,你想要多少,ATM就可以吐多少 >>> corner_street_atm = hsbc.create_atm() >>> print(corner_street_atm.next()) $100 >>> print(corner_street_atm.next()) $100 >>> print([corner_street_atm.next() for cash in range(5)]) ['$100', '$100', '$100', '$100', '$100'] >>> hsbc.crisis = True # 危機來臨,銀行沒錢了 >>> print(corner_street_atm.next()) <type 'exceptions.StopIteration'> >>> wall_street_atm = hsbc.ceate_atm() # 新建ATM,銀行仍然沒錢 >>> print(wall_street_atm.next()) <type 'exceptions.StopIteration'> >>> hsbc.crisis = False # 麻煩就是,即使危機過後銀行還是空的 >>> print(corner_street_atm.next()) <type 'exceptions.StopIteration'> >>> brand_new_atm = hsbc.create_atm() # 構造新的ATM,恢復業務 >>> for cash in brand_new_atm : ... print cash $100 $100 $100 $100 $100 $100 $100 $100 $100 |
對於訪問控制資源,生成器顯得非常有用。
迭代工具,你最好的朋友
迭代工具模組包含了操做指定的函式用於操作迭代器。想複製一個迭代器出來?連結兩個迭代器?以one liner(這裡的one-liner只需一行程式碼能搞定的任務)用內嵌的列表組合一組值?不使用list建立Map/Zip?···,你要做的就是 import itertools,舉個例子吧:
四匹馬賽跑到達終點排名的所有可能性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
>>> horses = [1, 2, 3, 4] >>> races = itertools.permutations(horses) >>> print(races) <itertools.permutations object at 0xb754f1dc> >>> print(list(itertools.permutations(horses))) [(1, 2, 3, 4), (1, 2, 4, 3), (1, 3, 2, 4), (1, 3, 4, 2), (1, 4, 2, 3), (1, 4, 3, 2), (2, 1, 3, 4), (2, 1, 4, 3), (2, 3, 1, 4), (2, 3, 4, 1), (2, 4, 1, 3), (2, 4, 3, 1), (3, 1, 2, 4), (3, 1, 4, 2), (3, 2, 1, 4), (3, 2, 4, 1), (3, 4, 1, 2), (3, 4, 2, 1), (4, 1, 2, 3), (4, 1, 3, 2), (4, 2, 1, 3), (4, 2, 3, 1), (4, 3, 1, 2), (4, 3, 2, 1)] |
理解迭代的內部機制:
迭代(iteration)就是對可迭代物件(iterables,實現了__iter__()方法)和迭代器(iterators,實現了__next__()方法)的一個操作過程。可迭代物件是任何可返回一個迭代器的物件,迭代器是應用在迭代物件中迭代的物件,換一種方式說的話就是:iterable物件的__iter__()方法可以返回iterator物件,iterator通過呼叫next()方法獲取其中的每一個值(譯者注),讀者可以結合Java API中的 Iterable介面和Iterator介面進行類比。
打賞支援我翻譯更多好文章,謝謝!
打賞譯者
打賞支援我翻譯更多好文章,謝謝!