摘要:可見(jiàn)裝飾器改變了函數(shù)的功能。裝飾器除了改變函數(shù)功能之外還有一個(gè)特性是,函數(shù)裝飾器在導(dǎo)入模塊時(shí)立即執(zhí)行,而被裝飾的函數(shù)只在明確調(diào)用時(shí)運(yùn)行。
什么是裝飾器
裝飾器是什么,簡(jiǎn)單來(lái)說(shuō),裝飾器可以改變一個(gè)函數(shù)的行為,比如原本有一個(gè)函數(shù)用來(lái)計(jì)算菲波那切數(shù)列,我們給這個(gè)函數(shù)加個(gè)計(jì)算執(zhí)行時(shí)間的裝飾器,這樣原來(lái)的函數(shù)不僅能夠計(jì)算菲波那切數(shù)列,而且還可以輸出計(jì)算花費(fèi)了多少時(shí)間。
在Python中,有幾個(gè)很常見(jiàn)的內(nèi)置裝飾器:比如@staticmethod, 它可以將一個(gè)類的方法聲明為靜態(tài)的。@property, 為類中的變量設(shè)置get和set方法,保證了封裝性。
如果你使用過(guò)python的web框架(比如flask)開(kāi)發(fā)過(guò)網(wǎng)站,你應(yīng)該經(jīng)常會(huì)見(jiàn)到裝飾器,像下面這樣:
@app.route("/") def hello(): return "Hello World!"
這段代碼把路由綁定到hello函數(shù)上,這樣你輸入網(wǎng)址之后就可以看到Hello World 。
先來(lái)看個(gè)很簡(jiǎn)單的例子:
# 定義了一個(gè)裝飾器 def deco(func): def hah(): print("hahha") return hah
上面我們定義了一個(gè)裝飾器,打印hahah,接下來(lái)使用:
# 使用這個(gè)裝飾器 @deco def lal(): pritn("lalalala") lal()
執(zhí)行l(wèi)al()會(huì)輸出hahha。 可見(jiàn)deco裝飾器改變了lal函數(shù)的功能。上面的代碼中,我們實(shí)際上是把lal函數(shù)放入了deco函數(shù),像這樣:
lal = deco(lal)
只不過(guò),直接使用@標(biāo)志把裝飾器放在某個(gè)函數(shù)上更方便一點(diǎn)而已。
裝飾器其實(shí)就是一個(gè)函數(shù)嵌套另一個(gè)函數(shù)(這里涉及到一個(gè)概念叫做閉包,下面會(huì)講到)。在裝飾器的定義中,需要把內(nèi)部的函數(shù)返回(像hah),內(nèi)部函數(shù)用來(lái)真正的改變被裝飾函數(shù)的功能。
不過(guò),上面定義的裝飾器好像沒(méi)什么用,我們來(lái)真正的寫(xiě)一個(gè)裝飾器,像文章開(kāi)頭說(shuō)的那樣,定義一個(gè)裝飾器計(jì)算函數(shù)執(zhí)行的時(shí)間。
實(shí)現(xiàn)一個(gè)簡(jiǎn)單的裝飾器import time # 這個(gè)裝飾器接收一個(gè)函數(shù)作為參數(shù) def clock(func): # clocked用來(lái)改變被裝飾函數(shù)功能 # 接收任意可變參數(shù) def clocked(*args): #先計(jì)算時(shí)間 t0 = time.perf_counter() # 然后運(yùn)行被裝飾的函數(shù) result = func(*args) # 計(jì)算運(yùn)行前后的時(shí)間差 elapsed = time.perf_counter()-t0 # 函數(shù)的名字 name = func.__name__ # 被裝飾函數(shù)的所有變量 arg_str = ",".join(repr(arg) for arg in args) # 輸出 print("[%0.8fs] %s(%s) -> %r" % (elapsed, name, arg_str, result)) # 返回被裝飾函數(shù)執(zhí)行結(jié)果 # 可見(jiàn)裝飾器是在原來(lái)的函數(shù)上增加了某些功能 # 而不是完全改變被裝飾函數(shù) return result # 把clocked函數(shù)返回 return clocked
來(lái)使用一下上面定義的裝飾器:
@clock def factorial(n): return 1 if n<2 else n*factorial(n-1) result = factorial(6) print(result)
執(zhí)行結(jié)果:
[0.00000030s] factorial(1) -> 1 [0.00004588s] factorial(2) -> 2 [0.00007184s] factorial(3) -> 6 [0.00060794s] factorial(4) -> 24 [0.00064205s] factorial(5) -> 120 [0.00066801s] factorial(6) -> 720 720
可以看到,在輸出計(jì)算結(jié)果的同時(shí),輸出了每一步的執(zhí)行時(shí)間。
裝飾器除了改變函數(shù)功能之外還有一個(gè)特性是,函數(shù)裝飾器在導(dǎo)入模塊時(shí)立即執(zhí)行,而被裝飾的函數(shù)只在明確調(diào)用時(shí)運(yùn)行。這點(diǎn)需要注意。
當(dāng)然了,裝飾器之上還可以放一個(gè)裝飾器,不過(guò)是多了一層嵌套而已。
python中還有一個(gè)內(nèi)置的模塊functools,這里面定義了一些常用的裝飾器函數(shù),幫助你更好地定義自己的裝飾器。這里就不講了。
閉包說(shuō)到閉包,在上面的代碼中我們已經(jīng)見(jiàn)識(shí)到了,函數(shù)中嵌套函數(shù)就是閉包。嚴(yán)格來(lái)說(shuō),閉包是指延伸了作用域的函數(shù),怎么理解?不如來(lái)看個(gè)例子:
我們定義一個(gè)函數(shù)不斷計(jì)算平均值,它會(huì)記住上一次計(jì)算的值進(jìn)行累計(jì)。
# 先看一些效果 avg = make_averager() print(avg(10)) print(avg(11)) print(avg(12))
輸出如下:
10.0 10.5 11.0
第一次輸出10,第二次輸出10加11的平均值,第三次輸出10加11加12的平均值。
怎么實(shí)現(xiàn)的?
def make_averager(): # 局部變量series # 用來(lái)保存每次輸入的值 series = [] def averager(new_value): series.append(new_value) total = sum(series) return total/len(series) return averager
上面的函數(shù)中,series是局部變量。當(dāng)我們調(diào)用avg(10)的時(shí)候,函數(shù)已經(jīng)返回了,按理說(shuō)它的本地作用域已經(jīng)不存在了,但是我們還是可以繼續(xù)使用。這是因?yàn)閟eries其實(shí)是自由變量,它不受本地作用域的限制。需要注意的是,對(duì)于不可變類型,需要顯示用關(guān)鍵字nonlocal 聲明自由變量,如果不聲明的話,會(huì)隱式的創(chuàng)建局部變量,這樣自由變量就會(huì)失效。而可變類型則不需要。比如,我們來(lái)更改一下上面的代碼:
# 改一下求平均值的函數(shù) # 用另一種方法 def make_averager(): count = 0 total = 0 def averager(new_value): # count、total是不可變類型 # 需要聲明為自由變量 nonlocal count, total count += 1 total += new_value return total / count return averager
除了上面說(shuō)的裝飾器的用法之外,我們還可以為裝飾器添加參數(shù),像app.route("/") 這樣,限于篇幅,下一篇文章再介紹。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/41224.html
摘要:先不講數(shù)據(jù)結(jié)構(gòu)了,這次來(lái)說(shuō)說(shuō)中一些不被注意的功能。直接交換第二個(gè)功能。對(duì)的長(zhǎng)度使用生成一個(gè)序列,然后遍歷或者這樣第三個(gè)功能。其實(shí)還接受第二個(gè)參數(shù),它的作用是在迭代的過(guò)程中如果碰到第二個(gè)參數(shù)則停止。 先不講數(shù)據(jù)結(jié)構(gòu)了,這次來(lái)說(shuō)說(shuō)python中一些不被注意的功能。 在python的設(shè)計(jì)哲學(xué)中,有這么一條內(nèi)容:Simple is better than complex,簡(jiǎn)單的代碼比復(fù)雜的要好...
摘要:來(lái)說(shuō)說(shuō)迭代器和生成器,還有可迭代對(duì)象和生成器表達(dá)式。有點(diǎn)繞是不是,其實(shí),一般只要知道可迭代對(duì)象以及它是如何實(shí)現(xiàn)的就行了,中常常用生成器來(lái)代替迭代器,可以說(shuō),生成器就是迭代器。 來(lái)說(shuō)說(shuō)迭代器和生成器,還有可迭代對(duì)象和生成器表達(dá)式。 之前簡(jiǎn)單的提到過(guò),一個(gè)對(duì)象是可迭代的可以理解為能夠使用for循環(huán)。這樣說(shuō)其實(shí)不太準(zhǔn)確,某個(gè)對(duì)象可迭代是因?yàn)樗鼉?nèi)部實(shí)現(xiàn)了$__iter__$這個(gè)特殊方法。比如在...
摘要:找出列表中小于的數(shù)據(jù)除了列表推導(dǎo)式,還有字典推導(dǎo)式,集合推導(dǎo)式,用法都一樣。如果你的數(shù)據(jù)量很大的話,考慮使用生成器表達(dá)式。切片不僅對(duì)列表有用,同樣適用于元組和字符串。切片命名使用方法,內(nèi)部參數(shù)與切片一樣。對(duì)剩余的的數(shù)據(jù),使用星號(hào)代替即可。 上次我們講了幾個(gè)不常見(jiàn)的數(shù)據(jù)類型,每個(gè)都有自己特殊的用途,雖然不經(jīng)常用到,了解一下也好。比如我們提到的數(shù)組類型,如果在數(shù)據(jù)量很大的時(shí)候同時(shí)要效率,就...
摘要:字典和集合都是基于散列表實(shí)現(xiàn)的,散列表也就是表,了解過(guò)數(shù)據(jù)結(jié)構(gòu)的應(yīng)該知道。而使用另一種辦法,任何鍵在找不到的情況下都會(huì)用中的值數(shù)據(jù)類型比如替換。在設(shè)計(jì)時(shí)就可以使用創(chuàng)建你的數(shù)據(jù)接口。 這次主要說(shuō)說(shuō)字典和集合這兩種數(shù)據(jù)類型。 字典和集合都是基于散列表實(shí)現(xiàn)的,散列表也就是hash表,了解過(guò)數(shù)據(jù)結(jié)構(gòu)的應(yīng)該知道。與散列表相關(guān)的一個(gè)概念叫做可散列,什么是可散列?在python官方定義中是這樣說(shuō)的:...
摘要:在中,特殊方法以雙下劃線開(kāi)始,以雙下劃線結(jié)束。真假值,如果向量模為,返回實(shí)現(xiàn)向量加法實(shí)現(xiàn)向量乘法,例如返回向量的模返回歐幾里德范數(shù)找個(gè)例子運(yùn)行下。怎么辦中有個(gè)特殊方法,可以修改控制臺(tái)輸出的樣式。 什么是特殊方法?當(dāng)我們?cè)谠O(shè)計(jì)一個(gè)類的時(shí)候,python中有一個(gè)用于初始化的方法$__init__$,類似于java中的構(gòu)造器,這個(gè)就是特殊方法,也叫作魔術(shù)方法。簡(jiǎn)單來(lái)說(shuō),特殊方法可以給你設(shè)計(jì)的...
閱讀 2212·2021-11-25 09:43
閱讀 1177·2021-11-23 09:51
閱讀 3511·2021-11-23 09:51
閱讀 3637·2021-11-22 09:34
閱讀 1573·2021-10-09 09:43
閱讀 2134·2019-08-30 15:53
閱讀 3171·2019-08-30 14:07
閱讀 579·2019-08-28 18:14