先搞清楚幾樣東西:函數名、函數體、傳回值,函數的記憶體位址、函數名加括号、函數名被當作參數、函數名加括号被當作參數、傳回函數名、傳回函數名加括号。
def outer(func):
def inner():
print("我是内層函數!")
return inner
def foo():
print("我是原始函數!")
outer(foo)
outer(foo())
函數名: foo、outer、inner
函數體:函數的整個代碼結構
傳回值: return後面的表達式
函數的記憶體位址:id(foo)、id(outer)等等
函數名加括号:對函數進行調用,比如foo()、outer(foo)
函數名作為參數: outer(foo)中的foo本身是個函數,但作為參數被傳遞給了outer函數
函數名加括号被當做參數:其實就是先調用函數,再将它的傳回值當做别的函數的參數,例如outer(foo())
傳回函數名:return inner
傳回函數名加括号:return inner(),其實就是先執行inner函數,再将其傳回值作為别的函數的傳回值。
裝飾器機制分析
def outer(func):
def inner():
print("認證成功!")
result = func()
print("日志添加成功")
return result
return inner
@outer
def f1():
print("業務部門1資料接口......")
1.程式開始運作,從上往下解釋,讀到def outer(func):的時候,發現這是個“一等公民”函數,于是把函數體加載到記憶體裡,然後過。
2.讀到@outer的時候,程式被@這個文法糖吸引住了,知道這是個裝飾器,按規矩要立即執行的,于是程式開始運作@後面那個名字outer所定義的函數。
3.程式傳回到outer函數,開始執行裝飾器的文法規則。規則是:被裝飾的函數的名字會被當作參數傳遞給裝飾函數。裝飾函數執行它自己内部的代碼後,會将它的傳回值指派給被裝飾的函數。原來的f1函數被當做參數傳遞給了func,而f1這個函數名之後會指向inner函數。

注意:@outer和@outer()有差別,沒有括号時,outer函數依然會被執行,這和傳統的用括号才能調用函數不同,需要特别注意!
另外,是f1這個函數名(而不是f1()這樣被調用後)當做參數傳遞給裝飾函數outer,也就是:func = f1,@outer等于outer(f1),實際上傳遞了f1的函數體,而不是執行f1後的傳回值。
還有,outer函數return的是inner這個函數名,而不是inner()這樣被調用後的傳回值。
4.程式開始執行outer函數内部的内容,一開始它又碰到了一個函數inner,inner函數定義塊被程式觀察到後不會立刻執行,而是讀入記憶體中(這是預設規則)。
5.再往下,碰到return inner,傳回值是個函數名,并且這個函數名會被指派給f1這個被裝飾的函數,也就是f1 = inner。根據前面的知識,我們知道,此時f1函數被新的函數inner覆寫了(實際上是f1這個函數名更改成指向inner這個函數名指向的函數體記憶體位址,f1不再指向它原來的函數體的記憶體位址),再往後調用f1的時候将執行inner函數内的代碼,而不是先前的函數體。那麼先前的函數體去哪了?還記得我們将f1當做參數傳遞給func這個形參麼?func這個變量儲存了老的函數在記憶體中的位址,通過它就可以執行老的函數體,你能在inner函數裡看到result = func()這句代碼,它就是這麼幹的!
6.接下來,還沒有結束。當業務部門,依然通過f1()的方式調用f1函數時,執行的就不再是舊的f1函數的代碼,而是inner函數的代碼。在本例中,它首先會列印個“認證成功”的提示,很顯然你可以換成任意的代碼,這隻是個示例;然後,它會執行func函數并将傳回值指派給變量result,這個func函數就是舊的f1函數;接着,它又列印了“日志儲存”的提示,這也隻是個示例,可以換成任何你想要的;最後傳回result這個變量。我們在業務部門的代碼上可以用r = f1()的方式接受result的值。
7.以上流程走完後,你應該看出來了,在沒有對業務部門的代碼和接口調用方式做任何修改的同時,也沒有對基礎平台部原有的代碼做内部修改,僅僅是添加了一個裝飾函數,就實作了我們的需求,在函數調用前進行認證,調用後寫入日志。這就是裝飾器的最大作用。
那麼為什麼我們要搞一個outer函數一個inner函數這麼複雜呢?一層函數不行嗎?
答:請注意,@outer這句代碼在程式執行到這裡的時候就會自動執行outer函數内部的代碼,如果不封裝一下,在業務部門還未進行調用的時候,就執行了,這和初衷不符。
ef outer(func):
print("認證成功!")
result = func()
print("日志添加成功")
return result
@outer
def f1():
print("業務部門1資料接口......")
# 業務部門并沒有調用f1函數
------------------------------------------
執行結果:
認證成功!
業務部門1資料接口......
日志添加成功
隻是定義好了裝飾器,業務部門還沒有調用f1函數呢,程式就把工作全做了。這就是為什麼要封裝一層函數的原因。
被裝飾函數的參數
一個參數
def outer(func):
def inner(username):
print("認證成功!")
result = func(username)
print("日志添加成功")
return result
return inner
@outer
def f1(name):
print("%s 正在連接配接業務部門1資料接口......"%name)
# 調用方法
f1("jack")
在inner函數的定義部分也加上一個參數,調用func函數的時候傳遞這個參數
多個參數
def outer(func):
def inner(*args,**kwargs):
print("認證成功!")
result = func(*args,**kwargs)
print("日志添加成功")
return result
return inner
@outer
def f1(name,age):
print("%s 正在連接配接業務部門1資料接口......"%name)
# 調用方法
f1("jack",18)
一個函數被多個函數裝飾
def outer1(func):
def inner(*args,**kwargs):
print("認證成功!")
result = func(*args,**kwargs)
print("日志添加成功")
return result
return inner
def outer2(func):
def inner(*args,**kwargs):
print("一條歡迎資訊。。。")
result = func(*args,**kwargs)
print("一條歡送資訊。。。")
return result
return inner
@outer1
@outer2
def f1(name,age):
print("%s 正在連接配接業務部門1資料接口......"%name)
# 調用方法
f1("jack",18)
#--------------------------------------------------
執行結果:
認證成功!
一條歡迎資訊。。。
jack 正在連接配接業務部門1資料接口......
一條歡送資訊。。。
日志添加成功
如何了解輸出的資訊?
多個裝飾器就像洋蔥皮,一層層包裹住最内部核心的原始函數。執行的時候逐層穿透進入最核心内部,執行内部核心函數後,再反向逐層穿回來。
裝飾器參數
# 認證函數
def auth(request,kargs):
print("認證成功!")
# 日志函數
def log(request,kargs):
print("日志添加成功")
# 裝飾器函數。接收兩個參數,這兩個參數應該是某個函數的名字。
def Filter(auth_func,log_func):
# 第一層封裝,f1函數實際上被傳遞給了main_fuc這個參數
def outer(main_func):
# 第二層封裝,auth和log函數的參數值被傳遞到了這裡
def wrapper(request,kargs):
# 下面代碼的判斷邏輯不重要,重要的是參數的引用和傳回值
before_result = auth(request,kargs)
if(before_result != None):
return before_result;
main_result = main_func(request,kargs)
if(main_result != None):
return main_result;
after_result = log(request,kargs)
if(after_result != None):
return after_result;
return wrapper
return outer
# 注意了,這裡的裝飾器函數有參數哦,它的意思是先執行filter函數
# 然後将filter函數的傳回值作為裝飾器函數的名字傳回到這裡,是以,
# 其實這裡,Filter(auth,log) = outer , @Filter(auth,log) = @outer
@Filter(auth,log)
def f1(name,age):
print("%s 正在連接配接業務部門1資料接口......"%name)
# 調用方法
f1("jack",18)
#-----------------------------------------------
運作結果:
認證成功!
jack 正在連接配接業務部門1資料接口......
日志添加成功