【python 裝飾器】深入理解python裝飾器
要想徹底搞懂Python中的裝飾器,除了需要有一點Python中的函式基礎,還需要解決如下四個問題。當我們解決了這四個問題後,也就徹底搞懂Python中的裝飾器。
1.什麼是裝飾器,其本質是什麼?
2.裝飾器有什麼作用?
3.裝飾器有什麼使用特點(使用原則)?
4.裝飾器的應用場景
提示:如果你還不知道Python中的函式,請先了解函式後,再來學習。
下面我們依次來回答。
第一部分:什麼是裝飾器,其本質是什麼?
裝飾器是什麼?
-
從字面意思我們大致可以推測出來,它的作用是用來裝飾的。日常生活中,大家都見過很多裝飾器,舉個最簡單的例子,套在iPhone外面的保護殼。保護殼的存在,並不會改變iPhone內部的功能,它存在的意義,在於增強了iPhone的抗摔效能。
-
Python中的裝飾器也是一樣的道理,它並不會改變被裝飾物件的內部邏輯,而是通過一種無侵入的方式,讓它獲得一些額外的能力,比如日誌記錄、許可權認證、失敗重試等等。
-
Python裝飾器看起來高深莫測,實際上它的實現原理非常簡單。我們知道,在Python中一切皆物件,函式作為一個特殊的物件,可以作為引數傳遞給另外一個函式,裝飾器的工作原理就是基於這一特性。裝飾器的預設語法是使用@來呼叫,這實際上僅僅是一種語法糖。
-
Python中的裝飾器並沒有什麼神祕的,其本質上就是個函式。可以用一個等式理解:裝飾器 = 高階函式 + 巢狀函式。
-
補充:
1.函式就是一個物件,函式名是一個指向該物件的變數。
2.高階函式:引數中有以函式為引數,或者返回值是函式的函式為高階函式
3.函式巢狀:函式裡面又定義了函式。
第二部分:.裝飾器有什麼作用?
- 裝飾器是一個程式設計利器,只需一處修改,任何被裝飾的物件就可以獲得額外的功能。
- 裝飾器,說白了就是用來增強其他函式功能的函式。其形式,通常是在被修飾函式定義時,用@裝飾器名修飾。
來個例子理解一下:
# encoding: utf-8 import datetime # 定義一個列印日誌的裝飾器 def my_log(func): def wrapper(*args, **kwargs): print("print_log") result = func(*args, **kwargs) print("{}函式呼叫時刻:{}".format(func.__name__, datetime.datetime.now())) return result return wrapper # 計算平方 @my_log def cal_square(x): result = x*x print("{} * {} = {}".format(x,x, result)) return result if __name__ == '__main__': x=2 result=cal_square(x) print result
自定義了一個計算一個數平方的函式cal_square,要求呼叫這個函式時列印其呼叫日誌,就可以寫一個日誌裝飾器,假設命名為my_log函式,用來裝飾所要呼叫的函式。
執行結果:
D:\Python27\python.exe F:/PycharmProjects/tom/裝飾器的作用.py
print_log
2 * 2 = 4
cal_square函式呼叫時刻:2018-10-01 21:55:06.638000
4
Process finished with exit code 0
說明:
1.在定義裝飾器時,入參傳入了一個函式,所以裝飾器是一個高階函式,也返回了一個內層函式名。
2.特別提醒: 外層函式的返回結果是巢狀函式wrapper函式名,而不是返回wrapper()。這兩個是不同的概念哦。
3.巢狀函式中的內層函式,函式名可以是任何符合Python命名規則的識別符號。我這裡命名wrapper,你可以自定義為其他合法的函式名。
4.為了保證原函式的呼叫入參不受任何影響,內層函式入參:*args, **kwargs。
5.為了保證原函式的返回值不受任何影響,我們用一個臨時變數result接收,並在內層函式進行返回了result。
6.你還記得前面提到的裝飾器的本質嗎?
其本質上就是個函式。可以用一個等式理解:裝飾器 = 高階函式 + 巢狀函式。
第三部分:裝飾器有什麼使用特點(使用原則)?
- 1.不會修改被裝飾的函式的原始碼。 (對函式的原始碼沒有任何修改,只是在原來功能的基礎上額外增強函式的功能。)
- 2.不會改變被裝飾函式的呼叫方式。 (原來怎麼呼叫,被裝飾後依舊怎麼呼叫。)
- 3.不會改變被裝飾函式的返回結果。
這三點使用特點非常重要,歸結為:原來的函式之前怎麼呼叫、怎麼入參、什麼樣的返回值,使用裝飾器裝飾後這些都不受任何影響。也就是:我們在不對原來函式的原始碼、呼叫形式、返回值等任何修改的情況下,增強了原來函式的功能。
4.裝飾器的應用場景
應用場景非常多,開發中常見的如:
1.插入日誌
2.效能測試
3.處理事務
4.開發python開源框架時,非常常用
5、連線重試
下面再來個裝飾器,在工作中應該用得著,我們知道,程式跑起來後,有一些因素往往是不可控的,比如網路的連通性。斷了怎麼樣,得重試是吧,下面我們定義一個重試機制的裝飾器,可以使用在專案中。
# 定義一個列印方法耗時的裝飾器
def my_time(func):
def wrapper(*args, **keywords):
start = time.time()
print("print_time")
result = func(*args, **keywords)
end = time.time()
t = end - start
print("{}方法執行耗時:{:.6}秒".format(func.__name__, t))
return result
return wrapper
# 定義一個重試機制的裝飾器
def retry(times=10):
def outer(f):
def inner(*args, **kwargs):
for i in xrange(times):
try:
return f(*args, **kwargs)
except Exception as e:
if (i + 1) < times:
pass
else:
raise e
return inner
return outer
使用一下
import random
@retry(10)
def non_steady():
if random.random() <= 0.5:
# 失敗的概率是 0.5
raise Exception("died")
else:
# 成功的概率是 0.5
return "survived"
kk=non_steady()
print kk
、D:\Python27\python.exe F:/PycharmProjects/tom/裝飾器的作用.py
survived
Process finished with exit code
# 計算平方
@my_time
@my_log
def cal_square(x):
time.sleep(3)
result = x*x
print("{} * {} = {}".format(x,x, result))
return result
cal_square(5)
D:\Python27\python.exe F:/PycharmProjects/tom/裝飾器的作用.py
print_time
print_log
5 * 5 = 25
cal_square函式呼叫時刻:2018-10-01 22:19:01.435000
wrapper方法執行耗時:3.0秒
Process finished with exit code 0
最後總之使用Python裝飾器,可以讓你的程式碼更易維護,可讀性也有一定提升。