前言
return :在程序函數中返回某個值,返回之后函數不在繼續執行,徹底結束。
yield : 帶有 yield 的函數是一個生成器,函數返回某個值時,會停留在某個位置,返回函數值后,會在前面停留的位置繼續執行,直到程序結束。
簡單理解: yield 就是 return 返回一個值,並且記住這個返回的位置,下次迭代就從這個位置后開始。
普通函數和生成器函數
假如要創建一個返回奇數數列的函數,普通函數的做法如下:
def odd_numbers(n): odd_num_list = [] for i in range(n): if (i % 2) == 1: odd_num_list.append(i) return odd_num_list for j in odd_numbers(10): print(j)
運行結果:
1 3 5 7 9
當將上述函數改成如下形式時,普通函數便變成了一個生成器函數。這時候調用 odd_numbers(10) 不會執行 odd_numbers 函數,而是返回一個生成器對象。
def odd_numbers(n): for x in range(n): if (x % 2) == 0: yield x num = odd_numbers(10) print(num)
運行結果:
<generator object odd_numbers at 0x7f9d77a095f0>
從生成器對象獲取值
為了從生成器對象獲取值,我們可以使用 for loop 、 next() 或者 list() 方法。
1、 for loop 方法:
def odd_numbers(n): for x in range(n): if (x % 2) == 1: yield x num = odd_numbers(10) for i in num: print(i)
2、next() 方法:
def odd_numbers(n): for x in range(n): if (x % 2) == 1: yield x num = odd_numbers(10) print(next(num)) print(next(num)) print(next(num)) print(next(num)) print(next(num))
3、 list() 方法
def odd_numbers(n): for x in range(n): if (x % 2) == 1: yield x num = odd_numbers(10) for i in list(num): print(i)
生成器對象只能被遍歷一次
生成器對象只能被遍歷一次,當我們再次使用時,生成器對象為空。
def odd_numbers(n): for x in range(n): if (x % 2) == 1: yield x num = odd_numbers(10) for i in num: print(i) # 再次使用 num print(list(num))
運行結果:
1 3 5 7 9 [] # 從輸出中可以看出,當再次使用 num 時,num 變成了空。
yield 對比 return
yield :
包含 yield 的函數在被調用時,返回一個生成器對象給調用者,只有在遍歷對象時,函數的代碼才會被執行。
生成器對象不占用內存。
適合處理數據量比較大的情況。
當數據量較大時,性能較好。
return :
return 返回一個值給調用者。
需要給返回的值分配內存。
當處理的數據量較小時,比較方便。
當數據量較大時,由於需要使用大量內存,會拖慢性能。
總結
- 返回的生成器對象在使用過一次后便不再可以使用,如果要獲取值,需要遍歷生成器對象。
- 可以使用for loop、next()或者list()方法從 生成器對象獲取值。
- yield和 return的最大區別是,yield 返回一個生成器對象給調用者,而return返回一個值給調用者。
- 使用yield時,不會將值存儲在內存中,這在處理的數據量很大時,比較有優勢。
舉例
def foo(): print("starting...") while True: res = yield 4 print("res:",res) g = foo() print(next(g)) print("*"*20) print(next(g))
運行結果:
starting... 4 ******************** res: None 4
詳細解釋:
1、程序開始執行以后,因為foo函數中有yield關鍵字,所以foo函數並不會真的執行,而是先得到一個生成器g(相當於一個對象)。
2、直到調用next方法,foo函數正式開始執行,先執行foo函數中的print方法,然后進入while循環。
3、程序遇到yield關鍵字,然后把yield想想成return,return了一個4之后,程序停止,並沒有執行賦值給res操作,此時next(g)語句執行完成,所以輸出的前兩行(第一個是while上面的print的結果,第二個是return出的結果)是執行print(next(g))的結果。
4、程序執行 print("*"*20) ,輸出20個*
5、又開始執行下面的 print(next(g)) ,這個時候和上面那個差不多,不過不同的是,這個時候是從剛才那個next程序停止的地方開始執行的,也就是要執行res的賦值操作;這時候要注意,這個時候賦值操作的右邊是沒有值的(因為剛才那個是return出去了,並沒有給賦值操作的左邊傳參數),所以這個時候res賦值是None,所以接着下面的輸出就是res:None。
6、程序會繼續在while里執行,又一次碰到yield,這個時候同樣return 出4,然后程序停止,print函數輸出的4就是這次return出的4。
python之生成器及其優點
迭代器協議
生成器自動實現了迭代器協議。
1、迭代器協議是指:對象需要提供next方法,它要么返回迭代中的下一項,要么就引起一個StopIteration異常,以終止迭代。
2、可迭代對象就是:實現了迭代器協議的對象。
3、協議是一種約定,可迭代對象實現迭代器協議,Python的內置工具(如for循環,sum,min,max函數等)使用迭代器協議訪問對象。
舉個例子:在所有語言中,我們都可以使用for循環來遍歷數組,Python的list底層實現是一個數組,所以,我們可以使用for循環來遍歷list。如下所示:
>>> for n in [1, 2, 3, 4]: ... print n
但是,其實Python的for循環不但可以用來遍歷list,還可以用來遍歷文件對象,如下所示:
with open(‘/etc/passwd’) as f: # 文件對象提供迭代器協議 for line in f: # for循環使用迭代器協議訪問文件 print line
為什么在Python中,文件還可以使用for循環進行遍歷呢?
這是因為,在Python中,文件對象實現了迭代器協議,for循環並不知道它遍歷的是一個文件對象,它只管使用迭代器協議訪問對象即可。正是由於Python的文件對象實現了迭代器協議,才得以使用如此方便的方式訪問文件,如下所示:
f = open('/etc/passwd') dir(f) # ['__class__', '__enter__', '__exit__', '__iter__', '__new__', 'writelines', '...'
生成器
Python使用生成器對延遲操作提供了支持。所謂延遲操作,是指在需要的時候才產生結果,而不是立即產生結果。這也是生成器的主要好處。
Python有兩種不同的方式提供生成器:
1、生成器函數:常規函數定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函數的狀態,以便下次重它離開的地方繼續執行。
2、生成器表達式:類似於列表推導,但是,生成器返回按需產生結果的一個對象,而不是一次構建一個結果列表。
生成器函數
舉例:使用生成器返回自然數的平方(注意返回的是多個值):
def gensquares(N): for i in range(N): yield i ** 2 for item in gensquares(5): print item
使用普通函數:
def gensquares(N): res = [] for i in range(N): res.append(i*i) return res for item in gensquares(5): print item
生成器表達式
使用列表推導,將會一次產生所有結果:【列表生成式】
squares = [x**2 for x in range(5)] print(squares) # [0, 1, 4, 9, 16]
將列表推導的中括號,替換成圓括號,就是一個生成器表達式:
squares = (x**2 for x in range(5)) print(squares) # <generator object at 0x00B2EC88> print(next(squares)) # 0 print(next(squares)) # 1 print(next(squares)) # 4 print(list(squares)) # [9, 16]
Python不但使用迭代器協議,讓for循環變得更加通用。
大部分內置函數,也是使用迭代器協議訪問對象的。例如, sum函數是Python的內置函數,該函數使用迭代器協議訪問對象,而生成器實現了迭代器協議,所以,我們可以直接這樣計算一系列值的和:
sum(x ** 2 for x in xrange(4))
再看生成器
前面已經對生成器有了感性的認識,我們以生成器函數為例,再來深入探討一下Python的生成器:
1、語法上和函數類似:生成器函數和常規函數幾乎是一樣的。它們都是使用def語句進行定義,差別在於,生成器使用yield語句返回一個值,而常規函數使用return語句返回一個值。
2、自動實現迭代器協議:對於生成器,Python會自動實現迭代器協議,以便應用到迭代背景中(如for循環,sum函數)。由於生成器自動實現了迭代器協議,所以,我們可以調用它的next方法,並且,在沒有值可以返回的時候,生成器自動產生StopIteration異常。
3、狀態掛起:生成器使用yield語句返回一個值。yield語句掛起該生成器函數的狀態,保留足夠的信息,以便之后從它離開的地方繼續執行
舉例:
首先,生成器的好處是延遲計算,一次返回一個結果。也就是說,它不會一次生成所有的結果,這對於大數據量處理,將會非常有用。
大家可以在自己電腦上試試下面兩個表達式,並且觀察內存占用情況。對於前一個表達式,我在自己的電腦上進行測試,還沒有看到最終結果電腦就已經卡死,對於后一個表達式,幾乎沒有什么內存占用。
sum([i for i in xrange(10000000000)]) sum(i for i in xrange(10000000000))
除了延遲計算,生成器還能有效提高代碼可讀性。
例如,現在有一個需求,求一段文字中,每個單詞出現的位置。
不使用生成器的情況:
def index_words(text): result = [] if text: result.append(0) for index, letter in enumerate(text, 1): if letter == ' ': result.append(index) return result
使用生成器的情況:
def index_words(text): if text: yield 0 for index, letter in enumerate(text, 1): if letter == ' ': yield index
這里,至少有兩個充分的理由說明 ,使用生成器比不使用生成器代碼更加清晰:
1、使用生成器以后,代碼行數更少。大家要記住,如果想把代碼寫的Pythonic,在保證代碼可讀性的前提下,代碼行數越少越好。
2、不使用生成器的時候,對於每次結果,我們首先看到的是result.append(index),其次,才是index。也就是說,我們每次看到的是一個列表的append操作,只是append的是我們想要的結果。使用生成器的時候,直接yield index,少了列表append操作的干擾,我們一眼就能夠看出,代碼是要返回index。
這個例子充分說明了,合理使用生成器,能夠有效提高代碼可讀性。只要大家完全接受了生成器的概念,理解了yield語句和return語句一樣,也是返回一個值。那么,就能夠理解為什么使用生成器比不使用生成器要好,能夠理解使用生成器真的可以讓代碼變得清晰易懂。
使用生成器的注意事項
直接舉例:
我們直接來看例子,假設文件中保存了每個省份的人口總數,現在,需要求每個省份的人口占全國總人口的比例。顯然,我們需要先求出全國的總人口,然后在遍歷每個省份的人口,用每個省的人口數除以總人口數,就得到了每個省份的人口占全國人口的比例。
def get_province_population(filename): with open(filename) as f: for line in f: yield int(line) gen = get_province_population('data.txt') all_population = sum(gen) #print all_population for population in gen: print population / all_population
執行上面這段代碼,將不會有任何輸出,這是因為,生成器只能遍歷一次。在我們執行sum語句的時候,就遍歷了我們的生成器,當我們再次遍歷我們的生成器的時候,將不會有任何記錄。所以,上面的代碼不會有任何輸出。
因此,生成器的唯一注意事項就是:生成器只能遍歷一次。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
前言
生成器是一個簡單的方式來完成迭代。簡單來說,Python的生成器是一個返回可以迭代對象的函數。
怎樣創建生成器
在一個一般函數中使用 yield 關鍵字,可以實現一個最簡單的生成器,此時這個函數變成一個生成器函數。
yield 與 return 返回相同的值,區別在於return返回后,函數狀態終止,而yield會保存當前函數的執行狀態,在返回后,函數又回到之前保存的狀態繼續執行。
生成器函數與一般函數的不同
1、生成器函數包含一個或者多個 yield 。
2、當調用生成器函數時,函數將返回一個對象,但是不會立刻向下執行。
3、像 __iter__() 和 __next__() 方法等是自動實現的,所以我們可以通過 next() 方法對對象進行迭代。
4、一旦函數被 yield ,函數會暫停,控制權返回調用者。
5、局部變量和它們的狀態會被保存,直到下一次調用。
6、函數終止的時候,StopIteraion會被自動拋出。
舉例
# 簡單的生成器函數 def my_gen(): n=1 print("first") # yield區域 yield n n+=1 print("second") yield n n+=1 print("third") yield n a=my_gen() print("next method:") # 每次調用a的時候,函數都從之前保存的狀態執行 print(next(a)) print(next(a)) print(next(a)) print("for loop:") # 與調用next等價的 b=my_gen() for elem in my_gen(): print(elem)
運行結果:
next method: first 1 second 2 third 3 for loop: first 1 second 2 third 3
使用循環的生成器
# 逆序yield出對象的元素 def rev_str(my_str): length=len(my_str) for i in range(length-1,-1,-1): yield my_str[i] for char in rev_str("hello"): print(char)
運行結果:
o
l
l
e
h
生成器的表達式
Python中,有一個列表生成方法,比如:
# 產生1,2,3,4,5的一個列表 [x for x in range(5)]
如果把[]換成(),那么會成為生成器的表達式。
print((x for x in range(5))) # <generator object <genexpr> at 0x0000017BD3427CF0>
舉例:
a = (x for x in range(10)) b = [x for x in range(10)] # 這是錯誤的,因為生成器不能直接給出長度 # print("length a:",len(a)) # 輸出列表的長度 print("length b:", len(b)) b = iter(b) # 二者輸出等價,不過b是在運行時開辟內存,而a是直接開辟內存 print(next(a)) print(next(b))
為什么使用生成器
1、更容易使用,代碼量較小。
2、內存使用更加高效。比如列表是在建立的時候就分配所有的內存空間,而生成器僅僅是需要的時候才使用。
3、代表了一個無限的流。如果我們要讀取並使用的內容遠遠超過內存,但是需要對所有的流中的內容進行處理,那么生成器是一個很好的選擇,比如可以讓生成器返回當前的處理狀態,由於它可以保存狀態,那么下一次直接處理即可。