這篇文章主要講解了“Python中的迭代器、生成器和裝飾器的功能”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Python中的迭代器、生成器和裝飾器的功能”吧!
在隆林等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強發(fā)展的系統(tǒng)性、市場前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供網(wǎng)站設(shè)計制作、做網(wǎng)站 網(wǎng)站設(shè)計制作定制網(wǎng)站建設(shè),公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),高端網(wǎng)站設(shè)計,全網(wǎng)營銷推廣,外貿(mào)網(wǎng)站建設(shè),隆林網(wǎng)站建設(shè)費用合理。
對于迭代器來說,我們更熟悉的應(yīng)該是可迭代對象,之前無論是源碼還是講課中或多或少我們提到過可迭代對象這個詞。之前為了便于大家理解可迭代對象,可能解釋的不是很正確,所以今天我們正式的聊一聊什么是可迭代對象。從字面意思來說,我們先對其進行拆解:什么是對象?Python中一切皆對象,之前我們講過的一個變量,一個列表,一個字符串,文件句柄,函數(shù)名等等都可稱作一個對象,其實一個對象就是一個實例,就是一個實實在在的東西。那么什么叫迭代?其實我們在日常生活中經(jīng)常遇到迭代這個詞兒,更新迭代等等,迭代就是一個重復(fù)的過程,但是不能是單純的重復(fù)(如果只是單純的重復(fù)那么他與循環(huán)沒有什么區(qū)別)每次重復(fù)都是基于上一次的結(jié)果而來。比如你爹生你,你生你爹,哦不對,你生你兒子,你兒子生你孫子等等,每一代都是不一樣的;還有你使用過得app,微信,抖音等,隔一段時間就會基于上一次做一些更新,那么這就是迭代??傻鷮ο髲淖置嬉馑紒碚f就是一個可以重復(fù)取值的實實在在的東西。
那么剛才我們是從字面意思分析的什么是可迭代對象,到目前為止我們接觸到的可迭代對象有哪些呢?
str list tuple dic set range 文件句柄等,那么int,bool這些為什么不能稱為可迭代對象呢?雖然在字面意思這些看著不符合,但是我們要有一定的判斷標準或者規(guī)則去判斷該對象是不是可迭代對象。
在python中,但凡內(nèi)部含有iter方法的對象,都是可迭代對象。
該對象內(nèi)部含有什么方法除了看源碼還有什么其他的解決方式么?當(dāng)然有了, 可以通過dir() 去判斷一個對象具有什么方法
s1 = 'alex' print(dir(s1))
dir()會返回一個列表,這個列表中含有該對象的以字符串的形式所有方法名。這樣我們就可以判斷python中的一個對象是不是可迭代對象了:
s1 = 'alex' i = 100 print('__iter__' in dir(i)) # False print('__iter__' in dir(s1)) # True
從字面意思來說:可迭代對象就是一個可以重復(fù)取值的實實在在的東西。
從專業(yè)角度來說:但凡內(nèi)部含有iter方法的對象,都是可迭代對象。
可迭代對象可以通過判斷該對象是否有’iter’方法來判斷。
可迭代對象的優(yōu)點:
可以直觀的查看里面的數(shù)據(jù)。
可迭代對象的缺點:
1. 占用內(nèi)存。
2. 可迭代對象不能迭代取值(除去索引,key以外)。
那么這個缺點有人就提出質(zhì)疑了,即使拋去索引,key以外,這些我可以通過for循環(huán)進行取值呀!對,他們都可以通過for循環(huán)進行取值,其實for循環(huán)在底層做了一個小小的轉(zhuǎn)化,就是先將可迭代對象轉(zhuǎn)化成迭代器,然后在進行取值的。那么接下來,我們就看看迭代器是個什么鬼。
從字面意思來說迭代器,是一個可以迭代取值的工具,器:在這里當(dāng)做工具比較合適。
從專業(yè)角度來說:迭代器是這樣的對象:實現(xiàn)了無參數(shù)的next方法,返回序列中的下一個元素,如果沒有元素了,那么拋出StopIteration異常.python中的迭代器還實現(xiàn)了iter方法,因此迭代器也可以迭代。 出自《流暢的python》
那么對于上面的解釋有一些超前,和難以理解,不用過于糾結(jié),我們簡單來說:在python中,內(nèi)部含有'Iter'方法并且含有'next'方法的對象就是迭代器。
ok,那么我們有了這個定義,我們就可以判斷一些對象是不是迭代器或者可迭代對象了了,請判斷這些對象:str list tuple dict set range 文件句柄 哪個是迭代器,哪個是可迭代對象:
o1 = 'alex' o2 = [1, 2, 3] o3 = (1, 2, 3) o4 = {'name': '太白','age': 18} o5 = {1, 2, 3} f = open('file',encoding='utf-8', mode='w') print('__iter__' in dir(o1)) # True print('__iter__' in dir(o2)) # True print('__iter__' in dir(o3)) # True print('__iter__' in dir(o4)) # True print('__iter__' in dir(o5)) # True print('__iter__' in dir(f)) # True # hsagn print('__next__' in dir(o1)) # False print('__next__' in dir(o2)) # False print('__next__' in dir(o3)) # False print('__next__' in dir(o4)) # False print('__next__' in dir(o5)) # False print('__next__' in dir(f)) # True f.close()
通過以上代碼可以驗證,之前我們學(xué)過的這些對象,只有文件句柄是迭代器,剩下的那些數(shù)據(jù)類型都是可迭代對象。
l1 = [1, 2, 3, 4, 5, 6] obj = l1.__iter__() # 或者 iter(l1)print(obj) # <list_iterator object at 0x000002057FE1A3C8>
可迭代對象是不可以一直迭代取值的(除去用索引,切片以及Key),但是轉(zhuǎn)化成迭代器就可以了,迭代器是利用next()進行取值:
l1 = [1, 2, 3,] obj = l1.__iter__() # 或者 iter(l1) # print(obj) # <list_iterator object at 0x000002057FE1A3C8> ret = obj.__next__() print(ret) ret = obj.__next__() print(ret) ret = obj.__next__() print(ret) ret = obj.__next__() # StopIteration print(ret) # 迭代器利用next取值:一個next取對應(yīng)的一個值,如果迭代器里面的值取完了,還要next, # 那么就報StopIteration的錯誤。
剛才我們提到了,for循環(huán)的循環(huán)對象一定要是可迭代對象,但是這不意味著可迭代對象就可以取值,因為for循環(huán)的內(nèi)部機制是:將可迭代對象轉(zhuǎn)換成迭代器,然后利用next進行取值,最后利用異常處理處理StopIteration拋出的異常。
l1 = [1, 2, 3, 4, 5, 6] # 1 將可迭代對象轉(zhuǎn)化成迭代器 obj = iter(l1) # 2,利用while循環(huán),next進行取值 while 1: # 3,利用異常處理終止循環(huán) try: print(next(obj)) except StopIteration: break
從字面意思來說:迭代器就是可以迭代取值的工具。
從專業(yè)角度來說:在python中,內(nèi)部含有'Iter'方法并且含有'next'方法的對象就是迭代器。
迭代器的優(yōu)點:
節(jié)省內(nèi)存。
迭代器在內(nèi)存中相當(dāng)于只占一個數(shù)據(jù)的空間:因為每次取值都上一條數(shù)據(jù)會在內(nèi)存釋放,加載當(dāng)前的此條數(shù)據(jù)。
惰性機制。
next一次,取一個值,絕不過多取值。
有一個迭代器模式可以很好的解釋上面這兩條:迭代是數(shù)據(jù)處理的基石。掃描內(nèi)存中放不下的數(shù)據(jù)集時,我們要找到一種惰性獲取數(shù)據(jù)項的方式,即按需一次獲取一個數(shù)據(jù)項。這就是迭代器模式。
迭代器的缺點:
不能直觀的查看里面的數(shù)據(jù)。
取值時不走回頭路,只能一直向下取值。
l1 = [1, 2, 3, 4, 5, 6] obj = iter(l1) for i in range(2): print(next(obj)) for i in range(2): print(next(obj))
我們今天比較深入的了解了可迭代對象與迭代器,接下來我們說一下這兩者之間比較與應(yīng)用:
可迭代對象:
是一個私有的方法比較多,操作靈活(比如列表,字典的增刪改查,字符串的常用操作方法等),比較直觀,但是占用內(nèi)存,而且不能直接通過循環(huán)迭代取值的這么一個數(shù)據(jù)集。
應(yīng)用:當(dāng)你側(cè)重于對于數(shù)據(jù)可以靈活處理,并且內(nèi)存空間足夠,將數(shù)據(jù)集設(shè)置為可迭代對象是明確的選擇。
迭代器:
是一個非常節(jié)省內(nèi)存,可以記錄取值位置,可以直接通過循環(huán)+next方法取值,但是不直觀,操作方法比較單一的數(shù)據(jù)集。
應(yīng)用:當(dāng)你的數(shù)據(jù)量過大,大到足以撐爆你的內(nèi)存或者你以節(jié)省內(nèi)存為首選因素時,將數(shù)據(jù)集設(shè)置為迭代器是一個不錯的選擇。(可參考為什么python把文件句柄設(shè)置成迭代器)。
什么是生成器?這個概念比較模糊,各種文獻都有不同的理解,但是核心基本相同。生成器的本質(zhì)就是迭代器,在python社區(qū)中,大多數(shù)時候都把迭代器和生成器是做同一個概念。不是相同么?為什么還要創(chuàng)建生成器?生成器和迭代器也有不同,唯一的不同就是:迭代器都是Python給你提供的已經(jīng)寫好的工具或者通過數(shù)據(jù)轉(zhuǎn)化得來的,(比如文件句柄,iter([1,2,3])。生成器是需要我們自己用python代碼構(gòu)建的工具。最大的區(qū)別也就如此了。
在python中有三種方式來創(chuàng)建生成器:
通過生成器函數(shù)
通過生成器推導(dǎo)式
python內(nèi)置函數(shù)或者模塊提供(其實1,3兩種本質(zhì)上差不多,都是通過函數(shù)的形式生成,只不過1是自己寫的生成器函數(shù),3是python提供的生成器函數(shù)而已)
我們先來研究通過生成器函數(shù)構(gòu)建生成器。
首先,我們先看一個很簡單的函數(shù):
def func(): print(11) return 22 ret = func() print(ret) # 運行結(jié)果: 11 22
將函數(shù)中的return換成yield,這樣func就不是函數(shù)了,而是一個生成器函數(shù)
def func(): print(11) yield 22
我們這樣寫沒有任何的變化,這是為什么呢? 我們來看看函數(shù)名加括號獲取到的是什么?
def func(): print(11) yield 22 ret = func() print(ret) # 運行結(jié)果: <generator object func at 0x000001A575163888>
運行的結(jié)果和最上面的不一樣,為什么呢?? 由于函數(shù)中存在yield,那么這個函數(shù)就是一個生成器函數(shù).
我們在執(zhí)行這個函數(shù)的時候.就不再是函數(shù)的執(zhí)行了.而是獲取這個生成器對象,那么生成器對象如何取值呢?
之前我們說了,生成器的本質(zhì)就是迭代器.迭代器如何取值,生成器就如何取值。所以我們可以直接執(zhí)行next()來執(zhí)行以下生成器
def func(): print("111") yield 222 gener = func() # 這個時候函數(shù)不會執(zhí)?. ?是獲取到?成器 ret = gener.__next__() # 這個時候函數(shù)才會執(zhí)? print(ret) # 并且yield會將func生產(chǎn)出來的數(shù)據(jù) 222 給了 ret。 結(jié)果: 111 222
并且我的生成器函數(shù)中可以寫多個yield。
def func(): print("111") yield 222 print("333") yield 444 gener = func() ret = gener.__next__() print(ret) ret2 = gener.__next__() print(ret2) ret3 = gener.__next__() # 最后?個yield執(zhí)?完畢. 再次__next__()程序報錯 print(ret3) 結(jié)果: 111 222 333 444
當(dāng)程序運行完最后一個yield,那么后面繼續(xù)運行next()程序會報錯,一個yield對應(yīng)一個next,next超過yield數(shù)量,就會報錯,與迭代器一樣。
yield與return的區(qū)別:
return一般在函數(shù)中只設(shè)置一個,他的作用是終止函數(shù),并且給函數(shù)的執(zhí)行者返回值。
yield在生成器函數(shù)中可設(shè)置多個,他并不會終止函數(shù),next會獲取對應(yīng)yield生成的元素。
舉例:
我們來看一下這個需求:老男孩向樓下賣包子的老板訂購了10000個包子.包子鋪老板非常實在,一下就全部都做出來了
def eat(): lst = [] for i in range(1,10000): lst.append('包子'+str(i)) return lst e = eat() print(e)
這樣做沒有問題,但是我們由于學(xué)生沒有那么多,只吃了2000個左右,剩下的8000個,就只能占著一定的空間,放在一邊了。如果包子鋪老板效率夠高,我吃一個包子,你做一個包子,那么這就不會占用太多空間存儲了,完美。
def eat(): for i in range(1,10000): yield '包子'+str(i) e = eat() for i in range(200): next(e)
這兩者的區(qū)別:
第一種是直接把包子全部做出來,占用內(nèi)存。
第二種是吃一個生產(chǎn)一個,非常的節(jié)省內(nèi)存,而且還可以保留上次的位置。
def eat(): for i in range(1,10000): yield '包子'+str(i) e = eat() for i in range(200): next(e) for i in range(300): next(e) # 多次next包子的號碼是按照順序記錄的。
·接下來我們再來認識一個新的東西,send方法
# next只能獲取yield生成的值,但是不能傳遞值。 def gen(name): print(f'{name} ready to eat') while 1: food = yield print(f'{name} start to eat {food}') dog = gen('alex') next(dog) next(dog) next(dog) # 而使用send這個方法是可以的。 def gen(name): print(f'{name} ready to eat') while 1: food = yield 222 print(f'{name} start to eat {food}') dog = gen('alex') next(dog) # 第一次必須用next讓指針停留在第一個yield后面 # 與next一樣,可以獲取到y(tǒng)ield的值 ret = dog.send('骨頭') print(ret) def gen(name): print(f'{name} ready to eat') while 1: food = yield print(f'{name} start to eat {food}') dog = gen('alex') next(dog) # 還可以給上一個yield發(fā)送值 dog.send('骨頭') dog.send('狗糧') dog.send('香腸')
send和next()區(qū)別:
相同點:
send 和 next()都可以讓生成器對應(yīng)的yield向下執(zhí)行一次。
都可以獲取到y(tǒng)ield生成的值。
不同點:
第一次獲取yield值只能用next不能用send(可以用send(None))。
send可以給上一個yield置傳遞值。
在python3中提供一種可以直接把可迭代對象中的每一個數(shù)據(jù)作為生成器的結(jié)果進行返回
# 對比yield 與 yield from def func(): lst = ['衛(wèi)龍','老冰棍','北冰洋','牛羊配'] yield lst g = func() print(g) print(next(g)) # 只是返回一個列表 def func(): lst = ['衛(wèi)龍','老冰棍','北冰洋','牛羊配'] yield from lst g = func() print(g) # 他會將這個可迭代對象(列表)的每個元素當(dāng)成迭代器的每個結(jié)果進行返回。 print(next(g)) print(next(g)) print(next(g)) print(next(g)) ''' yield from ['衛(wèi)龍','老冰棍','北冰洋','牛羊配'] 等同于: yield '衛(wèi)龍' yield '老冰棍' yield '北冰洋' yield '牛羊配' '''
有個小坑,yield from 是將列表中的每一個元素返回,所以 如果寫兩個yield from 并不會產(chǎn)生交替的效果
def func(): lst1 = ['衛(wèi)龍','老冰棍','北冰洋','牛羊配'] lst2 = ['饅頭','花卷','豆包','大餅'] yield from lst1 yield from lst2 g = func() for i in g: print(i)
什么是開放封閉原則?有的同學(xué)問開放,封閉這是兩個反義詞這還能組成一個原則么?這不前后矛盾么?其實不矛盾。開放封閉原則是分情況討論的。
我們的軟件一旦上線之后(比如你的軟件主要是多個函數(shù)組成的),那么這個軟件對功能的擴展應(yīng)該是開放的,比如你的游戲一直在迭代更新,推出新的玩法,新功能。但是對于源代碼的修改是封閉的。你就拿函數(shù)舉例,如果你的游戲源代碼中有一個函數(shù)是閃躲的功能,那么你這個函數(shù)肯定是被多個地方調(diào)用的,比如對方扔雷,對方開槍,對方用刀,你都會調(diào)用你的閃躲功能,那么如果你的閃躲功能源碼改變了,或者調(diào)用方式改變了,當(dāng)對方發(fā)起相應(yīng)的動作,你在調(diào)用你的閃躲功能,就會發(fā)生問題。所以,開放封閉原則具體具體定義是這樣:
1.對擴展是開放的
我們說,任何一個程序,不可能在設(shè)計之初就已經(jīng)想好了所有的功能并且未來不做任何更新和修改。所以我們必須允許代碼擴展、添加新功能。
2.對修改是封閉的
就像我們剛剛提到的,因為我們寫的一個函數(shù),很有可能已經(jīng)交付給其他人使用了,如果這個時候我們對函數(shù)內(nèi)部進行修改,或者修改了函數(shù)的調(diào)用方式,很有可能影響其他已經(jīng)在使用該函數(shù)的用戶。OK,理解了開封封閉原則之后,我們聊聊裝飾器。
什么是裝飾器?從字面意思來分析,先說裝飾,什么是裝飾? 裝飾就是添加新的,比如你家剛買的房子,下一步就是按照自己的喜歡的方式設(shè)計,進行裝修,裝飾,地板,墻面,家電等等。什么是器?器就是工具,也是功能,那裝飾器就好理解了:就是添加新的功能。
比如我現(xiàn)在不會飛,怎么才能讓我會飛?給我加一個翅膀,我就能飛了。那么你給我加一個翅膀,它會改變我原來的行為么?我之前的吃喝拉撒睡等生活方式都不會改變。它就是在我原來的基礎(chǔ)上,添加了一個新的功能。
今天我們講的裝飾器(裝修,翅膀)是以功能為導(dǎo)向的,就是一個函數(shù)。
被裝飾的對象:比如毛坯房,我本人,其實也是一個函數(shù)。0
所以裝飾器最終最完美的定義就是:在不改變原被裝飾的函數(shù)的源代碼以及調(diào)用方式下,為其添加額外的功能。
接下來,我們通過一個例子來為大家講解這個裝飾器:
需求介紹:你現(xiàn)在xx科技有限公司的開發(fā)部分任職,領(lǐng)導(dǎo)給你一個業(yè)務(wù)需求讓你完成:讓你寫代碼測試小ming寫的函數(shù)的執(zhí)行效率。
def index(): print('歡迎訪問博客園主頁')
版本1:
需求分析:你要想測試此函數(shù)的執(zhí)行效率,你應(yīng)該怎么做?應(yīng)該在此函數(shù)執(zhí)行前記錄一個時間, 執(zhí)行完畢之后記錄一個時間,這個時間差就是具體此函數(shù)的執(zhí)行效率。那么執(zhí)行時間如何獲取呢? 可以利用time模塊,有一個time.time()功能。
import time print(time.time())
此方法返回的是格林尼治時間,是此時此刻距離1970年1月1日0點0分0秒的時間秒數(shù)。也叫時間戳,他是一直變化的。所以要是計算shopping_car的執(zhí)行效率就是在執(zhí)行前后計算這個時間戳的時間,然后求差值即可。
import time def index(): print('歡迎訪問博客園主頁') start_time = time.time() index() end_time = time.time() print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')
由于index函數(shù)只有一行代碼,執(zhí)行效率太快了,所以我們利用time模塊的一個sleep模擬一下
import time def index(): time.sleep(2) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print('歡迎訪問博客園主頁') start_time = time.time() index() end_time = time.time() print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')
版本1分析:你現(xiàn)在已經(jīng)完成了這個需求,但是有什么問題沒有? 雖然你只寫了四行代碼,但是你完成的是一個測試其他函數(shù)的執(zhí)行效率的功能,如果讓你測試一下,小張,小李,小劉的函數(shù)效率呢? 你是不是全得復(fù)制:
import time def index(): time.sleep(2) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print('歡迎訪問博客園首頁') def home(name): time.sleep(3) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print(f'歡迎訪問{name}主頁') start_time = time.time() index() end_time = time.time() print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}') start_time = time.time() home('太白') end_time = time.time() print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}') ......
重復(fù)代碼太多了,所以要想解決重復(fù)代碼的問題,怎么做?我們是不是學(xué)過函數(shù),函數(shù)就是以功能為導(dǎo)向,減少重復(fù)代碼,好我們繼續(xù)整改。
版本2:
import time def index(): time.sleep(2) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print('歡迎訪問博客園主頁') def inner(): start_time = time.time() index() end_time = time.time() print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}') inner()
但是你這樣寫也是有問題的,你雖然將測試功能的代碼封裝成了一個函數(shù),但是這樣,你只能測試小ming同學(xué)的的函數(shù)index,你要是測試其他同事的函數(shù)呢?你怎么做?
import time def index(): time.sleep(2) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print('歡迎訪問博客園主頁') def home(name): time.sleep(3) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print(f'歡迎訪問{name}主頁') def inner(): start_time = time.time() index() home('太白') end_time = time.time() print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}') timer()
你要是像上面那么做,每次測試其他同事的代碼還需要手動改,這樣是不是太low了?所以如何變成動態(tài)測試其他函數(shù)?我們是不是學(xué)過函數(shù)的傳參?能否將被裝飾函數(shù)的函數(shù)名作為函數(shù)的參數(shù)傳遞進去呢?
版本3:
import time def index(): time.sleep(2) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print('歡迎訪問博客園主頁') def home(name): time.sleep(3) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print(f'歡迎訪問{name}主頁') def timmer(func): # func == index 函數(shù) start_time = time.time() func() # index() end_time = time.time() print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}') timmer(index)
這樣我將index函數(shù)的函數(shù)名作為參數(shù)傳遞給timmer函數(shù),然后在timmer函數(shù)里面執(zhí)行index函數(shù),這樣就變成動態(tài)傳參了。好,你們現(xiàn)在將版本3的代碼快速練一遍。 大家練習(xí)完了之后,發(fā)現(xiàn)有什么問題么? 對比著開放封閉原則說: 首先,index函數(shù)除了完成了自己之前的功能,還增加了一個測試執(zhí)行效率的功能,對不?所以也符合開放原則。 其次,index函數(shù)源碼改變了么?沒有,但是執(zhí)行方式改變了,所以不符合封閉原則。 原來如何執(zhí)行? index() 現(xiàn)在如何執(zhí)行? inner(index),這樣會造成什么問題? 假如index在你的項目中被100處調(diào)用,那么這相應(yīng)的100處調(diào)用我都得改成inner(index)。 非常麻煩,也不符合開放封閉原則。
版本4:實現(xiàn)真正的開放封閉原則:裝飾器。
這個也很簡單,就是我們昨天講過的閉包,只要你把那個閉包的執(zhí)行過程整清楚,那么這個你想不會都難。
import time def index(): time.sleep(2) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print('歡迎訪問博客園主頁') def home(name): time.sleep(3) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print(f'歡迎訪問{name}主頁')
你將上面的inner函數(shù)在套一層最外面的函數(shù)timer,然后將里面的inner函數(shù)名作為最外面的函數(shù)的返回值,這樣簡單的裝飾器就寫好了,一點新知識都沒有加,這個如果不會就得多抄幾遍,然后理解代碼。
def timer(func): # func = index def inner(): start_time = time.time() func() end_time = time.time() print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}') return inner # f = timer(index) # f()
我們分析一下,代碼,代碼執(zhí)行到這一行:f = timer(index) 先執(zhí)行誰?看見一個等號先要執(zhí)行等號右邊, timer(index) 執(zhí)行timer函數(shù)將index函數(shù)名傳給了func形參。內(nèi)層函數(shù)inner執(zhí)行么?不執(zhí)行,inner函數(shù)返回 給f變量。所以我們執(zhí)行f() 就相當(dāng)于執(zhí)行inner閉包函數(shù)。 f(),這樣既測試效率又執(zhí)行了原函數(shù),有沒有問題?當(dāng)然有啦!!版本4你要解決原函數(shù)執(zhí)行方式不改變的問題,怎么做? 所以你可以把 f 換成 index變量就完美了! index = timer(index) index()帶著同學(xué)們將這個流程在執(zhí)行一遍,特別要注意 函數(shù)外面的index實際是inner函數(shù)的內(nèi)存地址而不是index函數(shù)。讓學(xué)生們抄一遍,理解一下,這個timer就是最簡單版本裝飾器,在不改變原index函數(shù)的源碼以及調(diào)用方式前提下,為其增加了額外的功能,測試執(zhí)行效率。
你現(xiàn)在這個代碼,完成了最初版的裝飾器,但是還是不夠完善,因為你被裝飾的函數(shù)index可能會有返回值,如果有返回值,你的裝飾器也應(yīng)該不影響,開放封閉原則嘛。但是你現(xiàn)在設(shè)置一下試試:
import time def index(): time.sleep(2) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print('歡迎訪問博客園主頁') return '訪問成功' def timer(func): # func = index def inner(): start_time = time.time() func() end_time = time.time() print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}') return inner index = timer(index) print(index()) # None
加上裝飾器之后,他的返回值為None,為什么?因為你現(xiàn)在的index不是函數(shù)名index,這index實際是inner函數(shù)名。所以index() 等同于inner() 你的 '訪問成功'返回值應(yīng)該返回給誰?應(yīng)該返回給index,這樣才做到開放封閉,實際返回給了誰?實際返回給了func,所以你要更改一下你的裝飾器代碼,讓其返回給外面的index函數(shù)名。 所以:你應(yīng)該這么做:
def timer(func): # func = index def inner(): start_time = time.time() ret = func() end_time = time.time() print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}') return ret return inner index = timer(index) # inner print(index()) # print(inner())
借助于內(nèi)層函數(shù)inner,你將func的返回值,返回給了inner函數(shù)的調(diào)用者也就是函數(shù)外面的index,這樣就實現(xiàn)了開放封閉原則,index返回值,確實返回給了'index'。
讓同學(xué)們;練習(xí)一下。
到目前為止,你的被裝飾函數(shù)還是沒有傳參呢?按照我們的開放封閉原則,加不加裝飾器都不能影響你被裝飾函數(shù)的使用。所以我們看一下。
import time def index(): time.sleep(2) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print('歡迎訪問博客園主頁') return '訪問成功' def home(name): time.sleep(3) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print(f'歡迎訪問{name}主頁') def timer(func): # func = index def inner(): start_time = time.time() func() end_time = time.time() print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}') return inner # 要想timer裝飾home函數(shù)怎么做? home = timer(home) home('太白')
上面那么做,顯然報錯了,為什么? 你的home這個變量是誰?是inner,home('太白')實際是inner('太白')但是你的'太白'這個實參應(yīng)該傳給誰? 應(yīng)該傳給home函數(shù),實際傳給了誰?實際傳給了inner,所以我們要通過更改裝飾器的代碼,讓其將實參'太白'傳給home.
import time def index(): time.sleep(2) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print('歡迎訪問博客園主頁') return '訪問成功' def home(name): time.sleep(3) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print(f'歡迎訪問{name}主頁') def timer(func): # func = home def inner(name): start_time = time.time() func(name) # home(name) == home('太白') end_time = time.time() print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}') return inner # 要想timer裝飾home函數(shù)怎么做? home = timer(home) home('太白')
這樣你就實現(xiàn)了,還有一個小小的問題,現(xiàn)在被裝飾函數(shù)的形參只是有一個形參,如果要是多個怎么辦?有人說多少個我就寫多少個不就行了,那不行呀,你這個裝飾器可以裝飾N多個不同的函數(shù),這些函數(shù)的參數(shù)是不統(tǒng)一的。所以你要有一種可以接受不定數(shù)參數(shù)的形參接受他們。這樣,你就要想到*args,**kwargs。
import time def index(): time.sleep(2) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print('歡迎訪問博客園主頁') return '訪問成功' def home(name,age): time.sleep(3) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print(name,age) print(f'歡迎訪問{name}主頁') def timer(func): # func = home def inner(*args,**kwargs): # 函數(shù)定義時,*代表聚合:所以你的args = ('太白',18) start_time = time.time() func(*args,**kwargs) # 函數(shù)的執(zhí)行時,*代表打散:所以*args --> *('太白',18)--> func('太白',18) end_time = time.time() print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}') return inner home = timer(home) home('太白',18)
這樣利用*的打散與聚合的原理,將這些實參通過inner函數(shù)的中間完美的傳遞到給了相應(yīng)的形參。
好將上面的代碼在敲一遍。
代碼優(yōu)化:語法糖
根據(jù)我的學(xué)習(xí),我們知道了,如果想要各給一個函數(shù)加一個裝飾器應(yīng)該是這樣:
def home(name,age): time.sleep(3) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print(name,age) print(f'歡迎訪問{name}主頁') def timer(func): # func = home def inner(*args,**kwargs): start_time = time.time() func(*args,**kwargs) end_time = time.time() print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}') return inner home = timer(home) home('太白',18)
如果你想給home加上裝飾器,每次執(zhí)行home之前你要寫上一句:home = timer(home)這樣你在執(zhí)行home函數(shù) home('太白',18) 才是真生的添加了額外的功能。但是每次寫這一句也是很麻煩。所以,Python給我們提供了一個簡化機制,用一個很簡單的符號去代替這一句話。
def timer(func): # func = home def inner(*args,**kwargs): start_time = time.time() func(*args,**kwargs) end_time = time.time() print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}') return inner @timer # home = timer(home) def home(name,age): time.sleep(3) # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率 print(name,age) print(f'歡迎訪問{name}主頁') home('太白',18)
你看此時我調(diào)整了一下位置,你要是不把裝飾器放在上面,timer是找不到的。home函數(shù)如果想要加上裝飾器那么你就在home函數(shù)上面加上@home,就等同于那句話 home = timer(home)。這么做沒有什么特殊意義,就是讓其更簡單化,比如你在影視片中見過野戰(zhàn)軍的作戰(zhàn)時由于不方便說話,用一些簡單的手勢代表一些話語,就是這個意思。
至此標準版的裝飾器就是這個樣子:
def wrapper(func): def inner(*args,**kwargs): '''執(zhí)行被裝飾函數(shù)之前的操作''' ret = func '''執(zhí)行被裝飾函數(shù)之后的操作''' return ret return inner
這個就是標準的裝飾器,完全符合代碼開放封閉原則。這幾行代碼一定要背過,會用。
此時我們要利用這個裝飾器完成一個需求:簡單版模擬博客園登錄。此時帶著學(xué)生們看一下博客園,說一下需求: 博客園登陸之后有幾個頁面,diary,comment,home,如果我要訪問這幾個頁面,必須驗證我是否已登錄。 如果已經(jīng)成功登錄,那么這幾個頁面我都可以無阻力訪問。如果沒有登錄,任何一個頁面都不可以訪問,我必須先登錄,登錄成功之后,才可以訪問這個頁面。我們用成功執(zhí)行函數(shù)模擬作為成功訪問這個頁面,現(xiàn)在寫三個函數(shù),寫一個裝飾器,實現(xiàn)上述功能。
def auth(): pass def diary(): print('歡迎訪問日記頁面') def comment(): print('歡迎訪問評論頁面') def home(): print('歡迎訪問博客園主頁') 答案: login_status = { 'username': None, 'status': False, } def auth(func): def inner(*args,**kwargs): if login_status['status']: ret = func() return ret username = input('請輸入用戶名:').strip() password = input('請輸入密碼:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner @auth def diary(): print('歡迎訪問日記頁面') @auth def comment(): print('歡迎訪問評論頁面') @auth def home(): print('歡迎訪問博客園主頁') diary() comment() home()
我們看,裝飾器其實就是一個閉包函數(shù),再說簡單點就是兩層的函數(shù)。那么是函數(shù),就應(yīng)該具有函數(shù)傳參功能。
login_status = { 'username': None, 'status': False, } def auth(func): def inner(*args,**kwargs): if login_status['status']: ret = func() return ret username = input('請輸入用戶名:').strip() password = input('請輸入密碼:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner
你看我上面的裝飾器,不要打開,他可以不可在套一層:
def auth(x): def auth3(func): def inner(*args,**kwargs): if login_status['status']: ret = func() return ret username = input('請輸入用戶名:').strip() password = input('請輸入密碼:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner return auth
舉例說明:抖音:綁定的是微信賬號密碼。 皮皮蝦:綁定的是qq的賬號密碼。 你現(xiàn)在要完成的就是你的裝飾器要分情況去判斷賬號和密碼,不同的函數(shù)用的賬號和密碼來源不同。 但是你之前寫的裝飾器只能接受一個參數(shù)就是函數(shù)名,所以你寫一個可以接受參數(shù)的裝飾器。
def auth3(func): def inner(*args, **kwargs): if login_status['status']: ret = func() return ret if 微信: username = input('請輸入用戶名:').strip() password = input('請輸入密碼:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret elif 'qq': username = input('請輸入用戶名:').strip() password = input('請輸入密碼:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner @auth3 def jitter(): print('記錄美好生活') @auth3 def pipefish(): print('期待你的內(nèi)涵神評論')
解決方式:
def auth(x): def auth3(func): def inner(*args, **kwargs): if login_status['status']: ret = func() return ret if x == 'wechat': username = input('請輸入用戶名:').strip() password = input('請輸入密碼:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret elif x == 'qq': username = input('請輸入用戶名:').strip() password = input('請輸入密碼:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner return auth3 @auth('wechat') def jitter(): print('記錄美好生活') @auth('qq') def pipefish(): print('期待你的內(nèi)涵神評論')
log_dic = {"flag":False} def auth(argv): def wraaper(func): def inner(*args,**kwargs): if log_dic["flag"]: func(*args,**kwargs) elif argv == "QQ": print("歡迎登陸") user = input("username:") pwd = input("password:") if user == "rsx" and pwd == "rsx123": log_dic["flag"] = True func(*args,**kwargs) else: print("用戶名密碼錯誤") elif argv == "wechat": print("歡迎登陸") user = input("username:") pwd = input("password:") if user == "rsx" and pwd == "rsx123": log_dic["username"] = user func(*args, **kwargs) else: print("用戶名密碼錯誤") else: print("請選擇APP登陸") return inner return wraaper # msg=""" # QQ # wechat # 請選擇: # """ # chose = input(msg).upper() """ @auth("QQ") def foo(): print("騰訊,用心創(chuàng)造快樂") foo() """ # wraaper = auth("QQ") # foo = wraaper(foo) # foo() """ @auth("wechat") def foo(): print("微信") foo() """ # wraaper = auth("wechat") # foo = wraaper(foo) # foo()
def wrapper1(func): def inner1(*args,**kwargs): print(1) func(*args,**kwargs) print(11) return inner1 def wrapper2(func): def inner2(*args,**kwargs): print(2) func(*args, **kwargs) print(22) return inner2 def wrapper3(func): def inner3(*args,**kwargs): print(3) func(*args, **kwargs) print(33) return inner3 @wrapper3 @wrapper2 @wrapper1 def fo(): print(8) foo()
感謝各位的閱讀,以上就是“Python中的迭代器、生成器和裝飾器的功能”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Python中的迭代器、生成器和裝飾器的功能這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!
分享標題:Python中的迭代器、生成器和裝飾器的功能
轉(zhuǎn)載來于:http://aaarwkj.com/article22/psojcc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供ChatGPT、App設(shè)計、搜索引擎優(yōu)化、網(wǎng)站排名、手機網(wǎng)站建設(shè)、Google
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)