一、裝飾器定義
1)裝飾器:本質是函數。
2)功能:用來裝飾其他函數,顧名思義就是,為其他的函數添加附件功能的。
二、原則
1)不能修改被裝飾函數的源代碼
2)不能修改被裝飾函數的調用方式
如果你寫的這個程序在生產環境下已經運行了,如果修改別人的源代碼或者修改別人的調用方式,那么出了問題,后果可想而知
三、實現裝飾器知識儲備
1)函數即"變量"
2)高階函數
3)嵌套函數
最終: 高階函數+嵌套函數 => 裝飾器
?
3.1 函數即變量
1)python的內存機制
#變量
x?=?1
#函數
def?test():
pass
以上一個變量一個函數在內存中的表現形式如下圖:
?
在python解釋器中,有一個概念叫做引用基數,比方說,x=1,它會先在內存當中把1這個值實實在在的存放下來,這個x其實就是1的門牌號,也是對1的一次引用。python什么時候把這個1這個屋子清空吶?它會等到1所對應的門牌號都沒有了,就會把1這里面的東西給清掉,這個也是python的內存回收機制,就是靠這種方式回收的。
2)del清理
那我們用什么清理吶?用del去清理門牌號,就是對1的值引用的變量,del? x就表示清理掉1對應的x的門派號。如果x沒有被del,則x永遠不還被刪除,除非程序結束了,不然永遠不會被刪除。del刪除的不是1,只是把門牌號x刪除了,只是定期刷新時,發現1沒有被其他門牌號引用了,才會被清掉。
3)函數在內存的表現形式
①bar函數在foo函數之后定義
#bar函數在foo函數之后定義
def foo():print("in the foo")bar()def bar():print("in the bar")foo()
#輸出
in?the foo
in?the bar
②bar函數是在foo函數之前定義
# bar函數是在foo函數之前定義
def bar():print("in the bar")def foo():print("in the foo")bar()foo()
#輸出
in?the foo
in?the bar
顯然,兩種寫法效果是一樣的,那我們來看看第三種情況。
③bar函數在foo函數調用之后聲明
# bar函數在foo函數調用之后聲明
def foo():print("in the foo")bar()foo()def bar():print("in the bar")
#輸出
Traceback (most recent call last):
in?the foo
??File?"D:/PycharmProjects/pyhomework/day4/裝飾器/函數即變量.py", line?31,?in?<module>
????foo()
??File?"D:/PycharmProjects/pyhomework/day4/裝飾器/函數即變量.py", line?29,?in?foo
????bar()
NameError: name?'bar'?is?not?defined??#bar函數沒有定義
3.2 高階函數
實現高階函數有兩個條件:
1)把一個函數名當做實參傳給另外一個函數
2)返回值中包含函數名
1、把一個函數名當做實參傳給另外一個函數
作用:在不修改被裝飾函數源代碼的情況下為其添加功能
import time
def bar():time.sleep(3)print("in the bar")def test1(func):print(func) #相當于print(bar) 函數的內存地址start_time = time.time()func() #相當于bar() 進入函數內部執行stop_time = time.time()print("the func run the is %s"%(stop_time-start_time))
#沒有修改bar的代碼
test1(bar) #把bar函數名當做實參傳到test1中
#輸出
<function bar at?0x0000000000A7D378>??#bar函數的內存地址
in?the bar?????????????????????????????? #函數值
the func run the?is?2.9912972450256348
2、返回值中包括函數名
作用:不修改函數調用方式
import timedef bar():time.sleep(3)print("in the bar")def test2(func):print(func)return func #返回函數的內存地址#調用test2函數
bar = test2(bar) #重新給bar賦值,打印內存地址(內存地址加上小括號就能打印函數值)
bar() #bar函數調用方式不變,實現裝飾器的功能
相當于@bar
#輸出
<function bar at?0x0000000000B6D378>??#打印bar函數的內存地址
in?the bar
3.3 嵌套函數
1、定義
在一個函數的函數體內,用def 去聲明一個函數,而不是去調用其他函數,稱為嵌套函數。
嵌套函數例子:
def foo():print("in the foo")def bar(): #在foo函數體內,用def聲明一個函數print("in the bar")bar()
#調用foo函數
foo()
#輸出
in?the foo
in?the bar
下面這種情況是不是嵌套函數?
def bar():print("in the bar")def foo():print("in the foo")bar() #調用bar函數foo()
很顯然不是,因為只是調用了bar函數,沒有用def去聲明一個函數。
局部作用域和全局作用域的訪問順序
#局部作用域和全局作用域的訪問順序
x=0
def grandpa():x=1def dad():x=2def son():x=3print(x)son()dad()
#調用grandpa
grandpa()
很顯然最后輸出的是3,這個說明作用域:只能是從里往外找,一層一層的的找。
四、裝飾器實現
4.1 定義
裝飾器實現的條件:高階函數+嵌套函數 =》裝飾器
import time#定義內置函數
def timmer(func): #timmer(test1) func=test1def deco():start_time = time.time()func() #run test1()stop_time = time.time()print("the func run time is %s"%(stop_time-start_time))return deco #返回deco的內存地址#裝飾test1函數
@timmer
# 相當于test1 = timmer(test1) test1(),調用deco的內存值。同時也有定義一個test1的變量
def test1():time.sleep(3)print("in the test1")#直接執行test1函數
test1()
#輸出
in?the test1
the func run time?is?3.0002999305725098
執行步驟:
- 執行timmer函數,timmer(test1) 返回值賦值給test1變量,即test1=timmer(test1)
- 此時的test1的值是執行timmer函數返回值deco,即test1=deco
- 所以執行test1,其實就是執行的是deco函數,test1()其實就是執行deco函數。
4.2?執行函數帶參數
import timedef timmer(func): #timmer(test2) func=test2def deco():start_time = time.time()func() #run test2()stop_time = time.time()print("the func run time is %s"%(stop_time-start_time))return deco@timmer
def test2(name,age):print("name:%s,age:%s"%(name,age))test2()
#輸出
Traceback (most recent call last):
??File?"D:/PycharmProjects/pyhomework/day4/裝飾器/裝飾器高潮.py", line?23,?in?<module>
????test2()
??File?"D:/PycharmProjects/pyhomework/day4/裝飾器/裝飾器高潮.py", line?8,?in?deco
????func()???#run test1()
TypeError: test2() missing?2?required positional arguments:?'name'?and?'age'?#缺少傳入name和age參數
很顯然是錯誤的。因為這邊執行的test2函數其實就是執行的deco函數,deco函數體內的func()其實就是執行test2函數,但是,test2需要傳入name和age兩個參數,所以報錯。那怎么解決呢?
傳入確定參數:
import timedef timmer(func): #timmer(test1) func=test1def deco(name,age):start_time = time.time()func(name,age) #run test2()stop_time = time.time()print("the func run time is %s"%(stop_time-start_time))return deco@timmer
def test2(name,age):print("name:%s,age:%s"%(name,age))test2('zhou',22)
不能確定傳入幾個參數,所以我們用非固定參數傳參。代碼如下:
import timedef timmer(func): #timmer(test1) func=test1def deco(*args,**kwargs): #傳入非固定參數start_time = time.time()func(*args,**kwargs) #傳入非固定參數stop_time = time.time()print("the func run time is %s"%(stop_time-start_time))return deco#不帶參數
@timmer # 相當于test1 = timmer(test1)
def test1():time.sleep(3)print("in the test1")#帶參數
@timmer
def test2(name,age):print("name:%s,age:%s"%(name,age))
#調用
test1()
test2("Alex",22)
#輸出
#test1
in?the test1
the func run time?is?3.0010883808135986
#test2
name:Alex,age:22
the func run time?is?0.0??#test2
?
4.3 執行函數有返回值
def timmer(func): #timmer(test1) func=test1def deco(*args,**kwargs):res = func(*args,**kwargs) #這邊傳入函數結果賦給resreturn res # 返回resreturn deco@timmer
def test1(): # test1 = timmer(test1)print("in the test1")return "from the test1" #執行函數test1有返回值res = test1()
print(res)
#輸出
in?the test1
from?the test1
?
?通過上面的例子,可以看出,其實就是在內置函數中把傳入參數的執行結果賦給res,然后再返回res變量。
4.4帶參數裝飾器
之前我們的裝飾器都是沒有帶參數的,其實我們已經能解決90%的問題了,但是如果說有一種情況:就是在你訪問不通頁面時,你用的驗證的方式來源不同,這時你該怎么辦?
#本地驗證
user,passwd = "zhouqiongjie","abc123"def auth(auth_type): #傳遞裝飾器的參數print("auth func:",auth_type)def outer_wrapper(func): # 將被裝飾的函數作為參數傳遞進來def wrapper(*args,**kwargs): #將被裝飾函數的參數傳遞進來print("wrapper func args:",*args,**kwargs)username = input("Username:").strip()password = input("Password:").strip()if auth_type == "local":if user == username and passwd == password:print("\033[32mUser has passed authentication\033[0m")res = func(*args,**kwargs)print("--after authentication")return reselse:exit("Invalid username or password")elif auth_type == "ldap":passreturn wrapperreturn outer_wrapperdef index():print("welcome to index page")@auth(auth_type="local") #帶參數裝飾器
def home():print("welcome to home page")return "from home"@auth(auth_type="ldap") #帶參數裝飾器
def bbs():print("welcome to bbs page")index()
print(home())
bbs()
上面的例子可以看出,執行步驟:
1)??????? outer_wrapper = auth(auth_type="local")
2)??????? home = outer_wrapper(home)
3)home()
所以這個函數的作用分別是:
1)??????? auth(auth_type) 傳遞裝飾器的參數
2)??????? outer_wrapper(func) 把函數當做實參傳遞進來
3)wrapper(*args,**kwargs) 真正執行裝飾的函數