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