導讀:此文由伯樂線上 –劉志軍編譯自stackoverflow Python標籤中投票率最高的一個問題《The Python yield keyword explained》,e-satis 詳細回答了關於yield 以及 generator、iterable、iterator、iteration之間的關係。
可迭代物件(Iterbles)
建立一個列表(list)時,你可以逐個地讀取裡面的每一項元素,這個過程稱之為迭代(iteration)。
1 2 3 4 5 6 |
>>> mylist = [1, 2, 3] >>> for i in mylist: ... print(i) 1 2 3 |
mylist是一個可迭代物件。當使用列表推導式(list comprehension)建立了一個列表時,它就是一個可迭代物件:
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...
語句中的物件都可以叫做可迭代物件,例如:lists,strings,files等等。這些可迭代物件使用非常方便因為它能如你所願的儘可能讀取其中的元素,但是你不得不把所有的值儲存在記憶體中,當它有大量元素的時候這並不一定總是你想要的。
譯者補充:dict物件以及任何實現了__iter__()
或者__getitem__()
方法的類都是可迭代物件,此外,可迭代物件還可以用在zip,map等函式中,當一個可迭代物件作為引數傳遞給內建函式iter()
時,它會返回一個迭代器物件。通常沒必要自己來處理迭代器本身或者手動呼叫iter()
,for
語句會自動呼叫iter()
,它會建立一個臨時的未命名的變數來持有這個迭代器用於迴圈期間。 為了更好的理解yield,譯者引入了迭代器的介紹。
迭代器(iterator)
迭代器代表一個資料流物件,不斷重複呼叫迭代器的next()
方法可以逐次地返回資料流中的每一項,當沒有更多資料可用時,next()
方法會丟擲異常StopIteration。此時迭代器物件已經枯竭了,之後呼叫next()
方法都會丟擲異常StopIteration。迭代器需要有一個__iter()__
方法用來返回迭代器本身。因此它也是一個可迭代的物件。
生成器(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 i in mygenerator
(譯註:這裡作者所表達的意思是第二次執行達不到期望的效果)因為生成器只能使用一次:首先計算出0,然後計算出1,最後計算出4。
Yield
Yield是關鍵字,它類似於return,只是函式會返回一個生成器。
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 35 |
>>> class Bank(): # 建立銀行,構建ATM機,只要沒有危機,就可以不斷地每次從中取100 ... crisis = False ... def create_atm(self): ... while not self.crisis: ... yield "$100" >>> hsbc = Bank() # when everything's ok the ATM gives you as much as you want >>> 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.create_atm() # 即使建立一個新的ATM,銀行還是沒錢 >>> print(wall_street_atm.next()) <type 'exceptions.StopIteration'> >>> hsbc.crisis = False # 危機過後,銀行還是空的,因為該函式之前已經不滿足while條件 >>> 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 ... |
對於類似資源的訪問控制等場景,生成器顯得很實用。
Itertools是你最好的朋友
itertools模組包含一些特殊的函式用來操作可迭代物件。曾經想複製一個生成器?兩個生成器連結?在內嵌列表中一行程式碼處理分組?不會建立另外一個列表的Map/Zip函式?你要做的就是import itertools
。無例子無真相,我們來看看4匹馬賽跑到達終點所有可能的順序:
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)] |
理解迭代的內部機制
迭代是操作可迭代物件(實現了__iter__()
方法)和迭代器(實現了__next__()
方法)的過程。可迭代物件是任何你可以從其得到一個迭代器物件的任意物件(譯註:呼叫內建函式iter()),迭代器是能讓你在可迭代物件上進行迭代的物件(譯註:這裡好繞,迭代器實現了__iter__()
方法,因此它也是一個可迭代物件)。
最後更新時間:2015/06/13
打賞支援我翻譯更多好文章,謝謝!
打賞譯者
打賞支援我翻譯更多好文章,謝謝!