在代碼運行期間動態增加功能的方式,稱之爲“裝飾器”(Decorator)。
Python裝飾器接收一個函數,返回另一個函數。它其實是一個閉包(嵌套定義在非全局作用域裏面的函數能夠記住它在被定義的時候它所處的封閉命名空間),閉包的作用在下面的裝飾器使用中可以感受到,
而返回的函數就是裝飾過的函數,它可以給原函數增加一些附加功能,而不影響原函數的調用方式,簡直優雅至極!
增加函數功能
假設有這麼個簡單的函數:
def now():
print('Sbingo')
現在我們想在調用now時,先打印一些信息,怎麼做呢?
簡單,直接在now函數中打印就好了嘛!
But,這樣不利於代碼的複用,如果別的函數也要求打印呢?
那麼我們改一下,定義另一個函數,先執行附加功能,再調用now,實現需求。:
def log(f):
print('call %s():' %f.__name__)
now()
調用:log(now)
輸出:
call now():
Sbingo
一切看似完美,可是這樣更改後,每個調用now的地方都需要改爲調用log,十分麻煩。
裝飾器的簡單使用
這時候,就該裝飾器上場了!
更改log函數:
def log(f):
def wrapper(*args, **kw):
print('call %s():' % f.__name__)
return f(*args, **kw)
return wrapper
這裏log函數就是一個裝飾器,再利用語法糖@使用這個裝飾器定義now函數:
@log
def now():
print('Sbingo')
這樣定義好now之後,相當於執行了:
now = log(now)
這就是語法糖@幫我們做的事。
此時,log函數接收now函數,返回了wrapper函數,並賦值給了now!
所以此刻請對now函數另眼相看,這很重要。
而原先調用now函數的地方還是一樣的調用方式:
now()
卻得到了希望的輸出:
call now():
Sbingo
一切開始變得美好~
裝飾器帶參數
定義帶參數的裝飾器log:
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
可以看到,帶參數的裝飾器比之前多了1層,變成了3層嵌套。
使用裝飾器log定義now函數:
@log('2017')
def now():
print('Sbingo')
這樣定義好now之後,相當於執行了:
now = log('2017')(now)
再調用:now()
輸出:
2017 now():
Sbingo
裝飾器的調用順序
裝飾器可以疊加使用,讓我們來看一下疊加後的使用順序:
定義兩個裝飾器log和log_2:
def log(f):
print('enter log:')
def wrapper(*args, **kw):
print('log call %s():' % f.__name__)
return f(*args, **kw)
return wrapper
def log_2(f):
print('enter log_2:')
def wrapper(*args, **kw):
print('log_2 call %s():' % f.__name__)
return f(*args, **kw)
return wrapper
使用裝飾器log和log_2定義now函數:
@log
@log_2
def now():
print('Sbingo')
這樣定義好now之後,相當於執行了:
now = log(log_2(now))
於是,在定義時就會輸出:
enter log_2:
enter log:
再調用:now()
輸出:
enter log_2:
enter log:
log call wrapper():
log_2 call now():
Sbingo
還是很好理解的。
Python內置裝飾器
Python中有3個內置裝飾器,都是跟class相關的:staticmethod、classmethod 和property。
- staticmethod 是類靜態方法,其跟成員方法的區別是沒有 self 參數,並且可以在類不進行實例化的情況下調用
- classmethod 與成員方法的區別在於所接收的第一個參數不是 self (類實例的指針),而是cls(當前類的具體類型)
- property 是屬性的意思,利用它可以將類中的方法當做屬性使用。
總結
理解裝飾器的核心要義:
使用語法糖@定義目標函數後,目標函數已經是裝飾後的函數!
參考資料:
Python裝飾器學習(九步入門)
Python 裝飾器