當(dāng)數(shù)據(jù)量很少時(shí),,可以很快得到結(jié)果,。但是如果范圍擴(kuò)大到10000甚至是100000000,就會發(fā)現(xiàn)程序執(zhí)行時(shí)間會變長,,變卡,,甚至有可能會因超出內(nèi)存空間而出現(xiàn)程序崩潰的現(xiàn)象。這是因?yàn)楫?dāng)數(shù)據(jù)量變得非常大的時(shí)候,,內(nèi)存需要開辟很大的空間去存儲這些數(shù)據(jù),,內(nèi)存都被吃了,自然會變慢變卡,。使用生成器就能解決這個(gè)問題,。 對于上述同一個(gè)問題用生成器實(shí)現(xiàn)如下,將范圍擴(kuò)大到1-10000000: def lifang_generate(): '''求1-10000000所用整數(shù)的立方數(shù)-生成器方式實(shí)現(xiàn)''' for i in range(1,10000001): result = i ** 3 yield result if __name__ == '__main__': G = lifang_generate() 執(zhí)行效果如下: 可以看到?jīng)]有任何的結(jié)果輸出,這說明程序已經(jīng)可以順利執(zhí)行,。對于迭代器來講需要用next()方法來獲取值,,修改主函數(shù)為以下情況可以打印輸出前4個(gè)整數(shù)的立方數(shù): if __name__ == '__main__': G = lifang_generate() print(next(G)) print(next(G)) print(next(G)) print(next(G)) 輸出結(jié)果如下: 到此可以看到,,生成器生成的值需要使用next()方法一個(gè)一個(gè)的取,它不會一次性生成所有的計(jì)算結(jié)果,,只有在取值時(shí)才調(diào)用,,這時(shí)程序會返回計(jì)算的一個(gè)值且程序暫停;下一次取值時(shí)從上一次中斷了的地方繼續(xù)往下執(zhí)行,。 以取出前3個(gè)值為例,,下圖為生成器代碼解析圖: 圖解:Python解釋器從上往下解釋代碼,,首先是函數(shù)定義,這時(shí)在計(jì)算機(jī)內(nèi)存開辟了一片空間來存儲這個(gè)函數(shù),,函數(shù)沒有被執(zhí)行,,繼續(xù)往下解釋;到了主函數(shù)部分,,首先執(zhí)行藍(lán)色箭頭1,,接著往下執(zhí)行到藍(lán)色箭頭2第一次調(diào)用生成器取值,此時(shí)生成器函數(shù)lifang_generate()開始執(zhí)行,,執(zhí)行到生成器函數(shù)lifang_generate()的藍(lán)色箭頭2碰到y(tǒng)ield關(guān)鍵字,,這時(shí)候生成器函數(shù)暫停往下執(zhí)行并且將result的結(jié)果返回,由于是第一次執(zhí)行,,因此result存儲著1的立方的值,,此時(shí)將1返回,,第54行代碼print(first)將結(jié)果打印輸出。 主函數(shù)中程序接著往下執(zhí)行到藍(lán)色箭頭3,,生成器函數(shù)lifang_generate()第二次被調(diào)用,,與第一次不同,第二次從上一次(也就是第一次)暫停的位置繼續(xù)往下執(zhí)行,,上一次停在了yield處,,因此藍(lán)色箭頭3所作的事情就是執(zhí)行yield后面的語句,也就是第48行print('end'),,執(zhí)行完成之后因for循環(huán)條件滿足,,程序像第一次執(zhí)行那樣,執(zhí)行到y(tǒng)ield處暫停并返回一個(gè)值,,此時(shí)返回的是2的立方數(shù),,在第57行打印輸出8。 第三次調(diào)用(藍(lán)色箭頭4)與第二次類似,,在理清了執(zhí)行過程之后,,程序執(zhí)行結(jié)果如下: 迭代器這里先拋出兩個(gè)概念:可迭代對象,、迭代器。 凡是可以通過for循環(huán)遍歷其中的元素的對象,,都是可迭代對象,;之前學(xué)習(xí)得組合數(shù)據(jù)類型list(列表)、tuple(元組),、dict(字典),、集合(set)等,,上一小節(jié)介紹得生成器也可以使用for循環(huán)來遍歷,,因此,生成器也是迭代器,,但迭代器不一定就是生成器,,例如組合數(shù)據(jù)類型。 凡是可以通過next訪問取值得對象均為迭代器,,生成器就是一種迭代器,。可以看到,,生成器不僅可以用for循環(huán)來獲取值,,還可以通過next()來獲取。 Python中有一個(gè)庫collections,,通過該庫的Iterable方法來判斷一個(gè)對象是否是可迭代對象,;如果返回值為True則說明該對象為可迭代的,,返回值為False則說明該對象為不可迭代。用Iterator方法來判斷一個(gè)對象是否是迭代器,,根據(jù)返回值來判斷是否為迭代器,。 使用Iterable分別判斷列表,字典,,字符串以及一個(gè)整數(shù)類型是否是可迭代對象的代碼如下: from collections import Iterable def isiterable(): '''分別判斷列表,,字典,字符串100,,整形100是不是可迭代對象''' ls = isinstance([],Iterable) dic = isinstance({},Iterable) strs = isinstance('100',Iterable) ints = isinstance(100,Iterable) print('輸出True表示可迭代,,F(xiàn)alse表示不可迭代\n\ ls為{},dic為{},,strs為{},,ints為{}'.format(ls,dic,strs,ints)) def main(): isiterable() if __name__ == '__main__': main() 執(zhí)行的輸出結(jié)果如下: 使用Iterator判斷一個(gè)對象是否是迭代器的代碼如下,,與判斷是否為可迭代對象類似: from collections import Iterable,Iteratordef print_num(): '''定義一個(gè)產(chǎn)生斐波那契數(shù)列的生成器''' a,b = 0,1 for i in range(1,10): yield b a,b = b,a + b def isiterator(): '''分別判斷列表,字典,、生成器是否為迭代器''' ls_ret = isinstance([],Iterator) dict_ret = isinstance({},Iterator) genarate_ret = isinstance((x * 2 for i in range(10)),Iterator) print_num_ret = isinstance(print_num(),Iterator) print('輸出True表示該對象為迭代器,,F(xiàn)alse表示該對象不是迭代器\n\ ls輸出為{},dict輸出為{},,genarate輸出為{},,print_num輸出為{}'.format(ls_ret,dict_ret,genarate_ret,print_num_ret)) def main(): isiterator() if __name__ == '__main__': main() 輸出的結(jié)果如下: 組合數(shù)據(jù)類型不是迭代器,,但是屬于可迭代對象,可以通過iter()函數(shù)將其轉(zhuǎn)換位迭代器,,這樣就可以使用next方法來獲取對象各個(gè)元素的值,,代碼如下: from collections import Iterable,Iterator def trans_to_iterator(): '''使用iter()將可迭代類型-列表轉(zhuǎn)換為迭代器''' ls = [2,4,6,8,10] ls_ierator = iter(ls) ls_ierator_is = isinstance(ls_ierator,Iterator) print('轉(zhuǎn)換后的返回值為{},使用next取出的第一個(gè)元素的值為{}'.format(ls_ierator_is,next(ls_ierator)))def main(): trans_to_iterator() if __name__ == '__main__': main() 輸出結(jié)果為: 閉包內(nèi)部函數(shù)對外部函數(shù)變量的引用,則將該函數(shù)與用到的變量稱為閉包,。以下為閉包的例子: def func(num): print('start')def func_in():'''閉包內(nèi)容''' new_num = num ** 3 print(new_num)return func_in if __name__ == '__main__':ret = func(10)ret() 理解閉包是理解裝飾器的前提,,同樣通過一張圖來理解閉包的執(zhí)行過程: 圖解:Python解釋器從上往下解釋代碼,,首先定義一個(gè)函數(shù),,func指向了該函數(shù)(紅箭頭所示),;接著到主函數(shù)執(zhí)行第14行代碼 ret = func(10),此時(shí)先執(zhí)行賦值號“=”右邊的內(nèi)容,,這里調(diào)用了函數(shù)func()并傳入10這個(gè)實(shí)參,,函數(shù)func()代碼開始執(zhí)行,先是打印輸出“start”,,接著定義了一個(gè)函數(shù)func_in(),,func_in指向了該函數(shù),函數(shù)沒有被調(diào)用,,程序接著往下執(zhí)行,,return func_in 將函數(shù)的引用返回,第14行代碼用ret接收了這個(gè)返回值,,到此ret就指向了func_in所指向的函數(shù)體(綠箭頭所示),。最后執(zhí)行ret所指的函數(shù)。這就是閉包的整個(gè)過程,,func_in()函數(shù)以及該函數(shù)內(nèi)用到的變量num就稱為閉包,。 裝飾器代碼的編寫需要遵循封閉開放原則,封閉是指對于已有的功能代碼實(shí)現(xiàn)不允許隨意進(jìn)行修改,,開放是指能夠?qū)σ延械墓δ苓M(jìn)行擴(kuò)展,。例如一款手游,現(xiàn)在已經(jīng)能夠?qū)崿F(xiàn)現(xiàn)有的游戲模式,,但隨著外部環(huán)境的變化發(fā)展(市場競爭,,用戶體驗(yàn)等),現(xiàn)有的游戲模式已經(jīng)不能滿足用戶的需求了,。為了留住用戶,,需要加入更多的玩法來保持用戶對該款游戲的新鮮感,于是開發(fā)方在原來游戲的基礎(chǔ)上又開發(fā)了好幾種游戲模式,。像這樣,,新的游戲版本既增加了先的游戲模式,又保留了原有的游戲模式,,體現(xiàn)了封閉開放的原則,。 裝飾器的作用就是在不改變原來代碼的基礎(chǔ)上,在原來的功能上進(jìn)行拓展,,保證開發(fā)的效率以及代碼的穩(wěn)定性。 打印輸出九九乘法表可以通過以下代碼實(shí)現(xiàn): def func_1():'''打印輸出九九乘法表'''for i in range(1,10): for j in range(1,i + 1): result = i * j print('{}*{}={}'.format(i,j,result),end=' ') print('')if __name__ == '__main__':func_1() 輸出結(jié)果如下: 假如現(xiàn)在需要實(shí)現(xiàn)一個(gè)功能,,在不修改func_1函數(shù)代碼的前提下,,在九九乘法表前增加一個(gè)表頭說明,在乘法表最后也增加一個(gè)說明,。下面的代碼實(shí)現(xiàn)了裝飾器的功能: def shuoming(func):def shuoming_in(): print('以下為九九乘法表:') func() print('以上為九九乘法表')return shuoming_in def func_1():'''打印輸出九九乘法表'''for i in range(1,10): for j in range(1,i + 1): result = i * j print('{}*{}={}'.format(i,j,result),end=' ') print('') if __name__ == '__main__':func_1 = shuoming(func_1)func_1() 輸出結(jié)果如下: 可以看到func_1函數(shù)的代碼沒有任何修改,,還實(shí)現(xiàn)了問題提出的要求,,這其中的核心就在于最后兩行代碼。通過下圖來理解裝飾器執(zhí)行的過程: 圖解:跟之前一樣,,Python解釋器自上往下解釋代碼,遇到定義函數(shù)的代碼不用管,,因?yàn)闆]有調(diào)用函數(shù)是不會執(zhí)行的,;這樣直接就來到了第22行代碼中,程序先執(zhí)行賦值號“=”右邊的代碼,,shuoming(func_1)調(diào)用了之前定義的函數(shù),,并傳入了func_1實(shí)參,程序轉(zhuǎn)到shuoming(func)執(zhí)行,,形參func接收實(shí)參func_1,,此時(shí)func也指向了func_1所指向的函數(shù)(如圖中分界線上方白色方框內(nèi)的藍(lán)箭頭所示);在shuoming()函數(shù)中代碼繼續(xù)往下走,,在shuoming()函數(shù)內(nèi)容又定義了一個(gè)shuoming_in()函數(shù)(如圖中分界線上方白色方框內(nèi)的藍(lán)色方框所示),,接著往下,將shuoming_in()函數(shù)的引用返回,,至此shuoming()函數(shù)執(zhí)行完畢,,程序回到第22行代碼執(zhí)行,shuoming()函數(shù)的返回值被func_1接收,,此時(shí),,func_1不在指向原來的函數(shù),轉(zhuǎn)成指向shuoming_in所指向的函數(shù)(如圖中分界線下方白色方框內(nèi)的黃色箭頭),。最后調(diào)用func_1所指向的函數(shù),,也就是shuoming_in()函數(shù),shuoming_in()函數(shù)內(nèi)的func指向了原來func_1()所指的函數(shù)(也就是生成九九乘法表的函數(shù)),,因此程序最終的結(jié)果就在九九乘法表前后各加了一個(gè)說明性字符串,。 以上為裝飾器的執(zhí)行過程,但是以上裝飾寫法不夠簡潔,,大多數(shù)情況下采取以下寫法,,輸出結(jié)果是一樣的: def shuoming(func): def shuoming_in(): print('以下為九九乘法表:') func() print('以上為九九乘法表') return shuoming_in '''@shuoming相當(dāng)于func_1 = shuoming(fucn_1)'''@shuomingdef func_1(): '''打印輸出九九乘法表''' for i in range(1,10): for j in range(1,i + 1): result = i * j print('{}*{}={}'.format(i,j,result),end=' ') print('')if __name__ == '__main__': '''直接調(diào)用func_1即可完成裝飾''' func_1() 有時(shí)候有些被裝飾的函數(shù)可能有以下幾種情況:存在或不存在參數(shù),有返回值或沒有返回值,,參數(shù)可能定長或不定長等等,,為了通用性,與爬蟲的請求代碼一樣,裝飾器有著通用的寫法: def tongyong(func):def tongyong_in(*args,**kwargs): ret = func(*args,**kwargs) return retreturn tongyong_in 使用這個(gè)裝飾器裝飾九九乘法表一樣可以正常輸出,,如果需要特定的裝飾效果,,修改這個(gè)通用代碼即可。 |
|