pyhton中的yield關鍵字的作用

翻譯自: https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do

基本理解

爲了理解yield管自己的作用,你需要先理解什麼是生成器(generators).理解生成器之前,需要先理解可迭代對象(iterables)

可迭代對象(iterables)

當你創建一個list對象時,你可以逐個成員去遍歷,這種逐個成員遍歷讀取的操作叫做迭代(iteration)

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...		print(i)
...
1
2
3

這裏mylist就是一個可迭代對象。如果你使用列表解析(list comprehension,或翻譯成列表推導),你創建一個列表的同時,也創建了一個可迭代對象

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...		print(i)
...
0
1
4

每一種你可以使用for…in…語法操作的對象都是可迭代對象,比如lists、strings、files…
這些可迭代對象使用起來非常方便,你可以在需要的時候任意讀取使用,因爲所有的數據都存在內存(memory)裏。然而你並不總是希望這些數據都存在內存裏,尤其是當可迭代對象的數據量非常大的時候

生成器(generators)

生成器是迭代器(iterators),一種只能迭代一次的可迭代對象。生成器不會把所有的數據都存在內存裏,而是在執行時,動態生成數據(generate values on the fly)

>>> mygenerator = ( x*x for x in range(3))
>>> for i in mygenerator:
... 	print(i)
...
0
1
4

這段代碼和上一段相比,除了"[]“換成了”()",其他邏輯完全一樣,但是這裏的mygenerator已經不再是一個列表,而是一個生成器了。生成器只能被遍歷一次,所以這裏不能第二次執行for i in mygenerator。在第一個for…in…循環中,計算00,打印0,丟棄0 --> 計算11,打印1,丟棄1 --> 計算2*2,打印4,丟棄4

yield

yield是一個跟return用法類似的關鍵字,區別在於前者返回的是一個生成器

def createGenerator():
	mylist = range(3)
	for i in mylist:
		yield i*i

mygenerator = createGenerator()
print(mygenerator)
for i in mygenerator:
	print(i)

執行結果

<generator object createGenerator at 0x03662B30>
0
1
4

這個是個沒啥用的例子,但是當你的函數會返回一個巨大的數據集,而且這個數據集你只會使用一次時,這種方式用起來就很方便了
爲了理解yield,你必須理解當你調用你的函數時,你在這個函數裏面寫的代碼並不會被執行,這個函數只會返回一個生成器對象。 這個聽起來有點兒迷惑(a bit tricky)
然後,當遇到for代碼處理生成器時,代碼纔會從中斷的地方繼續執行
現在,最難的部分:
第一次用for語句訪問你的函數返回的生成器時,代碼會從函數的開頭一直執行到第一個yield處,返回第一次yield的值,後面的其他繼續訪問這個生成器的調用,將會從這個yield之後繼續執行到下一次遇到yield,返回yield的值,依此執行,直到沒有返回值
一旦函數執行完(邏輯上:因爲循環結束或者不滿足if/else條件導致的不會再執行yield操作),執行器中不會再有值

進階操作

控制一個生成器的消耗(controlling a generator exhaustion)

>>> class Bank(): # Let's create a bank, building ATMs
...    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 # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
...
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

說明:python3裏面,用next(corner_street_atm)或者corner_street_atm.next()代替corner_street_atm.next(),其他生成器亦然
這種操作針對各種與控制資源讀取類似的場景非常有用

itertools, your best friend

itertools庫包含管理迭代對象的各種特殊函數。可以實現諸如複製生成器、連接兩個生成器、對交織在一個list裏面的數據進行分組、在不創建新的list的情況下實現map/zip等操作。使用時直接import itertools即可
示例:一個四匹馬的賽馬所有可能的到達順序

>>> 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)是一中對可迭代對象(實現了__iter__()這個魔術方法)和迭代器(實現了__next__()這個魔術方法)的操作。可迭代對象(iterable)是包含迭代器的一種python對象。迭代器(iterator)是一個讓你能夠遍歷一個可迭代對象的python對象

更多關於for循環迭代的信息見這裏
協程相關的信息見官方文檔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章