說明
因爲是跟着視頻學習的,視頻裏說的內容有限,基本上說的內容我都會去網上找資料單獨加深擴展一下,但修飾器真的難住我了,聽着視頻裏說 修飾器如何如何牛逼,網上找資料,也說修飾器如何如何牛逼,但就因爲修飾器如何如何牛逼,所以我剛開始才連用都沒法正確使用嗎?
無論是視頻還是網上的博客,說修飾器的時候 都只說了修飾器是什麼,以及修飾器使用,卻沒人說 修飾器注意事項,導致剛開始接觸修飾器的人 會碰壁,會各種報錯。(因爲修飾器和我們平常使用def函數無異,但沒人說修飾器的使用和普通函數使用方法不一樣,所以就這容易誤導人)
閱讀了很多博客,跟着別人的博客做了很多實驗,敲了很多代碼,起初都是會有報錯,即使是跟着博客內容走的。 因爲基本上博客都沒有放運行結果圖,找的資料也有挺多重複的,而且很多博客代碼過於高深,對於我這種連修飾器都無法正確使用的人來說,確實有點爲難了。
所以 我結合別人博客的同時,自己也總結了使用方法 和注意事項。 相信看了這篇文章 對於修飾器的邏輯已經可以捋清楚了。
最後再囉嗦一下,學習的時候要先學原理,熟悉原理再擴展,無論是什麼語法,都要先學會語法和規則,代碼量上去了,語法原理沒問題,這時候纔開始往高階進展,所以 初期,網上找資料的時候 要學會主動過濾語法中 看不懂的代碼,別花精力去研究裏面功能代碼的意思,而是花心思研究 語法!
修飾器 從入門到…瞭解? 自信一點 ,到熟悉。。。
修飾器介紹
裝飾器:裝飾器本質上是一個函數,該函數用來處理其他函數,它可以讓其他函數在不需要修改代碼的前提下增加額外的功能(稱爲裝飾器Decoration,類似於AOP),裝飾器的返回值也是一個函數對象。它經常用於有切面需求的場景,比如:插入日誌、性能測試、事務處理、緩存、權限校驗等應用場景。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同代碼並繼續重用。概括的講,裝飾器的作用就是爲已經存在的對象添加額外的功能。
如果文字有點難理解,那麼就來一個圖 看看修飾器是啥
我們之前所理解的python執行函數過程是如圖1.1的流程.
如果我們給函數添加了修飾器(如圖1.2),那麼當程序執行到函數A的時候,系統會檢測到函數A上有一個修飾器,那麼系統就會先執行修飾器裏的過程然後再回到函數執行函數的內容.
注:從修飾器回來的那一條線路其實並不是必須的, 需要在修飾器裏面編寫回來的語句回到函數,下面會有說的(二級函數中調用一級函數的參數)
修飾器的簡單作用
說修飾器作用的時候,我們先說說需求吧,什麼時候能用得到修飾器。
比如,我現在有一個函數
def ccx():
print('my name is ccx')
print('I want to know the time')
如果我想知道現在時間,普通方法就是在當前函數加功能對吧,所以加上時間代碼如下
import time
def ccx():
print('my name is ccx')
print('I want to know the time')
print(' | ^ - ^ |' * 5)
start = time.time()
start1 = time.asctime(time.localtime(time.time()))
print('當前時間戳爲:', start)
print('當前時間爲:', start1)
ccx()
上面就是在一個函數中 實現添加時間的常規方法,但 如果 我們有10個函數,100個函數需要添加這個時間方法呢??? 複製代碼 然後挨個粘貼? 要是這樣的話,人生苦短 我用python,這就矛盾了啊。 所以 有了 修飾器的存在。
定義修飾器
修飾器的功能代碼 可以自定義,你只需要知道語法,以及怎麼定義的就行,後面修飾器的功能代碼 你想咋玩咋玩。
語法:
def name(x):
def name1():
# 功能代碼塊
x() #調用參數,如果不加這個,函數中的內容就不會被調用。
# 功能代碼塊
return name1()
如:現在定義一個 實現打印時間的 修飾器!
# 定義一個顯示時間的修飾器
import time # 時間模塊
def test(func):
def wrapper():
start = time.time()
start1 = time.asctime(time.localtime(time.time()))
func() # 用來輸出函數內容
time.sleep(5) #休眠 5秒
end = time.time()
end1 = time.asctime(time.localtime(time.time()))
print('start時間戳爲:',start)
print('start當前時間爲:', start1)
print('end時間戳爲:',end)
print('end當前時間爲:',end1)
return wrapper()
注意,單獨定義一個 修飾器,運行是沒有任何輸出的!!!因爲沒有調用!
使用修飾器
方法: 在使用裝飾器的函數上面加上 @修飾器名稱
, python的修飾器是一種語法糖(糖衣語法).
如,現在創建一個 ccx()函數,調用顯示時間的修飾器!!!
# 定義一個顯示時間的修飾器
import time # 時間模塊
def test(func):
def wrapper():
start = time.time()
start1 = time.asctime(time.localtime(time.time()))
func() # 用來輸出函數內容
time.sleep(5) #休眠 5秒
end = time.time() # 時間戳
end1 = time.asctime(time.localtime(time.time())) # 時間
print('start時間戳爲:',start)
print('start當前時間爲:', start1)
print('end時間戳爲:',end)
print('end當前時間爲:',end1)
return wrapper()
@test # 使用修飾器
def ccx():
print('my name is ccx')
print('I want to know the time')
注:使用修飾器,不能有調用 該函數,否則就會報錯!!! 因爲修飾器中已經自帶調用了,不能再單獨調用!!! 調用後報錯如下!!!
分析一下執行過程,使用修飾器執行效果等於下圖:
擴展內容
下面是一些擴展內容,感興趣的可以看看研究一下。
在《Python3程序開發指南第二版》(以下簡稱《指南》)中給的例子是一個對python初學者(沒涉及項目)來說比較有趣的小修飾器, 有興趣可以看看,我給它做了一點註釋
def positive_result(function):
def wrapper(*args, **kwargs):
# result獲得函數的返回值, 進行結果判斷
result = function(*args, **kwargs)
# assert斷言, 如果function函數的返回值大於等於0, 的產生一個AssertionError異常
assert result >= 0, function.__name__ + "() result isn't >= 0"
# 返回
return result
# 將wrapper的docstring和名稱設置成和原始函數一樣,有利於內省(獲得自身的信息)
wrapper.__name__ = function.__name__
wrapper.__doc__ = function.__doc__
return wrapper
# 使用positive_result修飾器
@positive_result
def discriminant(a,b,c):
return (b**2) - (4*a*c)
print(discriminant(0,6,5))
print(discriminant(2,6,5))
《指南》中給出了這個例子的簡化版, 使用到了functools.wraps(function)
def positive_result(function):
# wrapper本身使用functools模塊的@functools.wraps進行包裹, 這可以確保wrapper的名稱與docstring與function相同
@functools.wraps(function)
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)
assert result >= 0, function.__name__ + "() result isn't >= 0"
return result
return wrapper
- 修飾器參數化
現在我們已經瞭解到了什麼是修飾器以及修飾器的基本使用, 那麼在上面的日誌修飾器上, 我們的日誌信息往往是要寫入文件內,但是不同的函數需要寫進的文件名不一樣, 那麼簡單的@修飾器名稱
已經沒法滿足需要了, 這個時候就需要修飾器參數化, 即將要操作的文件名傳遞給test()函數
現在放一個《指南》中給出的例子
import functools
def bounded(mininum, maxinum):
def decorator(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)
if result < mininum:
return mininum
elif result > maxinum:
return maxinum
return result
return wrapper
return decorator
@bounded(0,100)
def percent(amount, total):
return (amount / total) * 100
percent(15,100)
執行過程如下: