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