裝飾器是什麼,簡單來講,裝飾器能夠改變一個函數的行爲,好比本來有一個函數用來計算菲波那切數列,咱們給這個函數加個計算執行時間的裝飾器,這樣原來的函數不只可以計算菲波那切數列,並且還能夠輸出計算花費了多少時間。python
在Python中,有幾個很常見的內置裝飾器:好比@staticmethod
, 它能夠將一個類的方法聲明爲靜態的。@property
, 爲類中的變量設置get和set方法,保證了封裝性。web
若是你使用過python的web框架(好比flask)開發過網站,你應該常常會見到裝飾器,像下面這樣:flask
@app.route("/") def hello(): return "Hello World!"
這段代碼把路由綁定到hello函數上,這樣你輸入網址以後就能夠看到Hello World
。閉包
先來看個很簡單的例子:app
# 定義了一個裝飾器 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),內部函數用來真正的改變被裝飾函數的功能。code
不過,上面定義的裝飾器好像沒什麼用,咱們來真正的寫一個裝飾器,像文章開頭說的那樣,定義一個裝飾器計算函數執行的時間。作用域
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('/')
這樣,限於篇幅,下一篇文章再介紹。