连Python产生器(Generator)的原理都解释不了,还敢说Python用了5年?
最近有很多學(xué)Python同學(xué)問我,Python Generator到底是什么東西,如何理解和使用。Ok,現(xiàn)在就用這篇文章對(duì)Python Generator做一個(gè)敲骨瀝髓的深入解析。
?
為了更好地理解產(chǎn)生器(Generator),還需要掌握另外兩個(gè)東西:yield和迭代(iterables)。下面就迭代、產(chǎn)生器和yield分別做一個(gè)深入的解析。
?
1. 迭代
?
當(dāng)創(chuàng)建一個(gè)列表對(duì)象后,可以一個(gè)接一個(gè)讀取列表中的值,這個(gè)過程就叫做迭代。
mylist = [1, 2, 3] for i in mylist:print(i, end = ' ')?
mylist對(duì)象是可迭代的。在創(chuàng)建列表時(shí),可以使用列表推導(dǎo)表達(dá)式,所以從直觀上看,列表是可迭代的。
?
mylist = [x*x for x in range(3)] for i in mylist:print(i, end=' ')??
只要使用for ... in...語句,那么in子句后面的部分一定是一個(gè)可迭代的對(duì)象,如列表、字典、字符串等。
?
這些可迭代的對(duì)象在使用上非常容易理解,我們可以用自己期望的方式讀取其中的值。但會(huì)帶來一個(gè)嚴(yán)重的問題。就拿列表為例,如果需要迭代的值非常多,這就意味著需要先將所有的值都放到列表中,而且即使迭代完了列表中所有的值,這些值也不會(huì)從內(nèi)存中消失(至少還會(huì)存在一會(huì))。而且如果這些值只需要迭代一次就不再使用,那么這些值在內(nèi)存中長(zhǎng)期存在是沒有必要的,所有就產(chǎn)生了產(chǎn)生器(Generator)的概念。
?
2. 產(chǎn)生器(Generator)
?
要理解產(chǎn)生器,首先要清楚產(chǎn)生器到底要解決什么問題,以及產(chǎn)生器的特性。
?
產(chǎn)生器只解決一個(gè)問題,就是讓需要迭代的值不再常駐內(nèi)存,也就是解決的內(nèi)存資源消耗的問題。
為了解決這個(gè)問題,產(chǎn)生器也要付出一定的代價(jià),這個(gè)代價(jià)就是產(chǎn)生器中的值只能訪問一次,這也是產(chǎn)生器的特性。
?
下面先看一個(gè)最簡(jiǎn)單的產(chǎn)生器的例子:
?
# 創(chuàng)建產(chǎn)生器 data_generator = (x*x for x in range(3)) print(data_generator) for i in data_generator:print(i, end=' ') print() print('第二次迭代data_generator,什么都不會(huì)輸出') print() for i in data_generator:print(i, end=' ')?
?
乍一看這段代碼,好像與前面的代碼沒什么區(qū)別。其實(shí),只有一點(diǎn)點(diǎn)區(qū)別,就是在創(chuàng)建data_generator對(duì)象時(shí)使用了一對(duì)圓括號(hào),而不是一對(duì)方括號(hào)。使用一對(duì)方括號(hào)創(chuàng)建的是列表對(duì)象,而使用一對(duì)圓括號(hào)創(chuàng)建的就是迭代器對(duì)象,如果直接輸出,會(huì)輸出迭代器對(duì)象的地址,只有通過for...in...語句或調(diào)用迭代器的相應(yīng)方法才能輸出迭代器對(duì)象中的值。而且第二次對(duì)迭代器對(duì)象進(jìn)行迭代,什么都不會(huì)輸出,這是因?yàn)榈髦荒鼙坏淮?#xff0c;而且被迭代的值使用完,是不會(huì)再保存在內(nèi)存中的。有點(diǎn)類似熊瞎子摘苞米,摘一穗,丟一穗。
?
?
?
?
執(zhí)行這段代碼,會(huì)輸出如下結(jié)果:
?
<generator object <genexpr> at 0x7f95e0154150> 0 1 4 第二次迭代data_generator,什么都不會(huì)輸出?
3. yield
?
到現(xiàn)在為止,我們已經(jīng)對(duì)產(chǎn)生器要解決的問題,以及特性有了一個(gè)基本了解,那么產(chǎn)生器是如何做到這一點(diǎn)的呢?這就要依靠yield語句了。
?
現(xiàn)在讓我們先來看一個(gè)使用yield的例子。
?
?
? # 編寫產(chǎn)生器函數(shù) def generate_even(max):for i in range(0, max + 1):if i % 2 == 0:yield i print(generate_even(10)) even_generator = generate_even(10) for n in even_generator:print(n, end=' ')?
?
這段代碼的目的是輸出不大于10的所有偶數(shù),其中g(shù)enerator_even是一個(gè)產(chǎn)生器函數(shù)。我們注意到,在該函數(shù)中每找到一個(gè)偶數(shù),就會(huì)通過yield語句指定這個(gè)偶數(shù)。那么這個(gè)yield起什么作用呢?
?
再看看后面的代碼,首先調(diào)用generator_even函數(shù),并將返回值賦給even_generator變量,這個(gè)變量的類型其實(shí)是一個(gè)產(chǎn)生器對(duì)象。而for...in...循環(huán)中的in子句后面則是這個(gè)產(chǎn)生器對(duì)象,而n則是產(chǎn)生器中的每一個(gè)值(偶數(shù))。執(zhí)行這段代碼,會(huì)輸出如下結(jié)果:
?
<generator object generate_even at 0x7f814826c450> 0 2 4 6 8 10?
現(xiàn)在先談?wù)剤?zhí)行yield語句會(huì)起到什么效果。其實(shí)yield語句與return語句一樣,都起到返回的作用。但yield與return不同,如果執(zhí)行return語句,會(huì)直接返回return后面表達(dá)式的值。但執(zhí)行yield語句,返回的是一個(gè)產(chǎn)生器對(duì)象,而且這個(gè)產(chǎn)生器對(duì)象的當(dāng)前值就是yield語句后面跟著的表達(dá)式的值。調(diào)用yield語句后,當(dāng)前函數(shù)就會(huì)返回一個(gè)迭代器,而且函數(shù)會(huì)暫停執(zhí)行,直到對(duì)該函數(shù)進(jìn)行下一次迭代。
?
可能讀到這些解釋,有的讀者還是不太明白,什么時(shí)候進(jìn)行下一次迭代呢?如果不使用for...in...語句,是否可以對(duì)產(chǎn)生器進(jìn)行迭代呢?其實(shí)迭代器有一個(gè)特殊方法__next__。每次對(duì)迭代器的迭代,本質(zhì)上都是在調(diào)用__next__方法。
?
那么還有最后一個(gè)問題,for...in...語句在什么時(shí)候才會(huì)停止迭代呢?其實(shí)for...in...語句在底層會(huì)不斷調(diào)用in子句后面的可迭代對(duì)象的__next__方法,直到該方法拋出StopIteration異常為止。也就是說,可以將上面的for...in...循環(huán)改成下面的代碼。連續(xù)調(diào)用6次__next__方法,返回0到10,一共6個(gè)偶數(shù),當(dāng)?shù)?次調(diào)用__next__方法時(shí),產(chǎn)生器中已經(jīng)沒有值了,所以會(huì)拋出StopIteration異常。由于for...in...語句自動(dòng)處理了StopIteration異常,所以循環(huán)就會(huì)自動(dòng)停止,但當(dāng)直接調(diào)用__next__方法時(shí),如果產(chǎn)生器中沒有值了,就會(huì)直接拋出StopIteration異常,除非使用try...except...語句捕獲該異常,否則程序會(huì)異常中斷。
?
even_generator = generate_even(10) print(even_generator.__next__()) print(even_generator.__next__()) print(even_generator.__next__()) print(even_generator.__next__()) print(even_generator.__next__()) print(even_generator.__next__()) # print(even_generator.__next__()) # 拋出StopIteration異常?
總結(jié):產(chǎn)生器本質(zhì)上就是動(dòng)態(tài)產(chǎn)生待迭代的值,使用完就直接扔掉了,這樣非常節(jié)省內(nèi)存空間,但這些值只能被迭代一次。
?
4. 用普通函數(shù)模擬產(chǎn)生器函數(shù)的效果
?
如果你看到一個(gè)函數(shù)中使用了yield語句,說明該函數(shù)是一個(gè)產(chǎn)生器。其實(shí)可以按下面的步驟將該產(chǎn)生器函數(shù)改造成普通函數(shù)。
?
1. 在函數(shù)的開始部分定義一個(gè)列表變量,代碼如下:
result = []2. 將所有的yield expr語句都替換成下面的語句:
result.append(expr)3. 函數(shù)的最后執(zhí)行return result返回這個(gè)列表對(duì)象
?
?
為了更清晰表明這個(gè)轉(zhuǎn)換過程,現(xiàn)在給出一個(gè)實(shí)際的案例:
?
?
# 產(chǎn)生不大于max的偶數(shù) def generate_even(max):for i in range(0, max + 1):if i % 2 == 0:yield ieven_generator = generate_even(10) for n in even_generator:print(n, end=' ')# 將產(chǎn)生器函數(shù)改造成普通函數(shù),實(shí)際上,就是將yield后面表達(dá)式的值都添加在列表中 def generate_even1(max):evens = []for i in range(0, max + 1):if i % 2 == 0:evens.append(i)return evens print() even_list = generate_even1(10) for n in even_list:print(n, end=' ')?
在這段代碼中有兩個(gè)函數(shù):generate_even和generate_even1,其中g(shù)enerate_even是產(chǎn)生器函數(shù),generate_even1是普通函數(shù)(與generate_even函數(shù)的功能完全相同)。按著前面的步驟,將所有產(chǎn)生的偶數(shù)都添加到了列表變量evens中,最后返回這個(gè)列表變量。這兩個(gè)函數(shù)在使用方式上完全相同。不過從本質(zhì)上說,generate_even函數(shù)是動(dòng)態(tài)生成的偶數(shù),用完了就扔,而generate_even1函數(shù)事先將所有產(chǎn)生的偶數(shù)都添加到列表中,最后返回。所以從generate_even1函數(shù)的改造過程來看,yield的作用就相當(dāng)于使用append方法將表達(dá)式的值添加到列表中,只不過yield并不會(huì)保存表達(dá)式的值,而append方法會(huì)保存表達(dá)式的值。
?
?5.與迭代相關(guān)的API
?
這一節(jié)來看一看Python為我們提供了哪些與迭代相關(guān)的API
?
Python SDK提供了一個(gè)itertools模塊,該模塊中的API都與迭代相關(guān),例如,可以通過chain.from_iterable方法合并多個(gè)可迭代對(duì)象,通過permutations函數(shù)以可迭代對(duì)象形式返回列表的全排列。
?
from itertools import *# 這里每一個(gè)yield的值必須是可迭代的,才能用chain.from_iterable方法合并 def make_iterables_to_chain():yield [1,2,3]yield ['a','b','c']yield ['hello','world']for v in make_iterables_to_chain():print(v) # 將所有可迭代對(duì)象合并成一個(gè)可迭代對(duì)象 for v in chain.from_iterable(make_iterables_to_chain()):print('<',v,'>', end = ' ') print('-------上面的代碼相當(dāng)于下面的寫法-------') a = [1,2,3] a.extend(['a','b','c']) a.extend(['hello','world']) print(a) for v in a:print('[',v,']', end = ' ') # 以可迭代對(duì)象形式返回列表的全排列 values = [1,2,3,4] values_permutations = permutations(values) for p in values_permutations:print(p)?
?
執(zhí)行這段代碼,會(huì)輸出如下內(nèi)容:
?
[1, 2, 3] ['a', 'b', 'c'] ['hello', 'world'] < 1 > < 2 > < 3 > < a > < b > < c > < hello > < world > -------上面的代碼相當(dāng)于下面的寫法------- [1, 2, 3, 'a', 'b', 'c', 'hello', 'world'] [ 1 ] [ 2 ] [ 3 ] [ a ] [ b ] [ c ] [ hello ] [ world ] (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)?
總結(jié)
以上是生活随笔為你收集整理的连Python产生器(Generator)的原理都解释不了,还敢说Python用了5年?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 古典文学--本经阴符七术
- 下一篇: python greenlet快速学习(