歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> 如何理解Python關鍵字yield

如何理解Python關鍵字yield

日期:2017/3/1 9:26:26   编辑:Linux編程

兩年前開始接觸Python,在SO上看到一篇關於yield的文章,講解不錯,於是嘗試將其翻譯成了中文,後來譯文收到了不少吐槽,於是兩年後的今天對其文重新理解一篇,遂有了此文,譯文加入了大量譯注信息,幫助讀者更好的理解。
(譯注:以下代碼必須在Python3環境下運行)在理解yield之前,你需要明白生成器(generator)是什麼?生成器又源自於迭代對象。

可迭代對象(Iterbles)

創建一個列表(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等等。這些可迭代對象使用非常方便因為它能如你所願的盡可能讀取其中的元素,但是你不得不把所有的值存儲在內存中,當它有大量元素的時候這並不一定總是你想要的。

譯者補充:dict對象以及任何實現了__iter__()或者__getitem__()方法的類都是可迭代對象,此外,可迭代對象還可以用在zip,map等函數中,當一個可迭代對象作為參數傳遞給內建函數iter()時,它會返回一個迭代器對象。通常沒必要自己來處理迭代器本身或者手動調用iter()for語句會自動調用iter(),它會創建一個臨時的未命名的變量來持有這個迭代器用於循環期間。 為了更好的理解yield,譯者引入了迭代器的介紹。

迭代器(iterator)

迭代器代表一個數據流對象,不斷重復調用迭代器的next()方法可以逐次地返回數據流中的每一項,當沒有更多數據可用時,next()方法會拋出異常StopIteration。此時迭代器對象已經枯竭了,之後調用next()方法都會拋出異常StopIteration。迭代器需要有一個__iter()__方法用來返回迭代器本身。因此它也是一個可迭代的對象。

生成器(Generators)

生成器也是一個迭代器,但是你只可以迭代他們一次,不能重復迭代,因為它並沒有把所有值存儲在內存中,而是實時地生成值:

>>> 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,只是函數會返回一個生成器。

>>> 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,你必須清楚的是:當函數被調用時,函數體中的代碼是不會運行的,函數僅僅是返回一個生成器對象。這裡理解起來可能稍微有點復雜。函數中的代碼每次會在for循環中被執行,接下來是最難的一部分:

for第一次調用生成器對象時,代碼將會從函數的開始處運行直到遇到yield為止,然後返回此次循環的第一個值,接著循環地執行函數體,返回下一個值,直到沒有值返回為止。
一旦函數運行再也沒有遇到yield時,生成器就被認為是空的。這有可能是因為循環終止,或者因為沒有滿足任何if/else

控制生成器的窮舉

>>> 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匹馬賽跑到達終點所有可能的順序:

>>> 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__()方法,因此它也是一個可迭代對象)。

下面關於Python的文章您也可能喜歡,不妨看看:

Python:在指定目錄下查找滿足條件的文件 http://www.linuxidc.com/Linux/2015-08/121283.htm

Python2.7.7源碼分析 http://www.linuxidc.com/Linux/2015-08/121168.htm

無需操作系統直接運行 Python 代碼 http://www.linuxidc.com/Linux/2015-05/117357.htm

CentOS上源碼安裝Python3.4 http://www.linuxidc.com/Linux/2015-01/111870.htm

《Python核心編程 第二版》.(Wesley J. Chun ).[高清PDF中文版] http://www.linuxidc.com/Linux/2013-06/85425.htm

《Python開發技術詳解》.( 周偉,宗傑).[高清PDF掃描版+隨書視頻+代碼] http://www.linuxidc.com/Linux/2013-11/92693.htm

Python腳本獲取Linux系統信息 http://www.linuxidc.com/Linux/2013-08/88531.htm

在Ubuntu下用Python搭建桌面算法交易研究環境 http://www.linuxidc.com/Linux/2013-11/92534.htm

Python 語言的發展簡史 http://www.linuxidc.com/Linux/2014-09/107206.htm

Python 的詳細介紹:請點這裡
Python 的下載地址:請點這裡

Copyright © Linux教程網 All Rights Reserved