裝飾器
- 裝飾器定義;
- 本質是函數 : 函數的目的是完成特定的功能
- 裝飾器功能:一個裝飾其他函數功能的函數(為其他函數添加特定的功能)
拋出問題:
假如我們現在有10個函數,每個函數都有自己獨特的功能,但是,現在我們需要給這10個函數添加一個記錄日志的功能
# 定義日志的函數,然后將日志函數添加到test的十個函數中 def logger(): print("...logger...") def test1(): pass logger() def test2(): pass logger() def test3(): pass logger() test1() test2() test3()
特定場景:假如,這10個函數已經再線上運行了,比如說,現在需要再用戶已經使用的軟件中,給這10個函數添加新的功能,那么該怎么做?
-
- 如果我們直接修改函數的源代碼,可能會導致軟件崩潰,所以原則上,我們不能在已經上線的程序中修改源代碼。
裝飾器原則:
- 原則1:不能修改被裝飾的函數的源代碼
- 原則2:不能修改被裝飾的函數的調用方式
- 裝飾器對被裝飾的函數是完全透明的,函數感知不到裝飾器的存在,因為函數的源代碼和調用方式都沒有改變
初識裝飾器:
- 計算函數運行時間的裝飾器
import time # 創建獲取函數運行時間的裝飾器 def timmer(func): def warpper(*args, **kwargs): start_time = time.time() func() stop_time = time.time() print("the func run time is %s" % (stop_time-start_time)) return warpper @timmer #為test函數添加獲取test函數運行時間的功能 def test(): time.sleep(3) print("in the test") test() """ 運行效果: in the test the func run time is 3.004085063934326 """
- 由上代碼可以得知:
- 裝飾器本身就是一個函數
- 裝飾器不修改被裝飾函數的源代碼、同時也不修改被裝飾函數的調用方式
- 對於test函數來說,裝飾器timmer就和不存在一樣
實現裝飾器的知識儲備:
- 函數即”變量“
- 高階函數
- 嵌套函數
高階函數 + 嵌套函數 =》 裝飾器
函數即“變量”:
# 第一種 def foo(): print("in the foo") bar() foo() 第二種 def foo(): print("in the foo") bar() def bar(): print("in the bar") foo() # 第三種 def bar(): print("in the bar") def foo(): print("in the foo") bar() foo() # 第四種 def foo(): print("in the foo") bar() foo() def bar(): print("in the bar")
高階函數:
-
把一個函數名當作實參傳遞給另一個函數:
符合裝飾器的條件之一import time def bar(): time.sleep(3) print("in the bar") # print(bar) # bar記錄了bar函數在內存中的地址 def foo(func): start_time = time.time() func() stop_time = time.time() print("the func run time is %s" % (stop_time - start_time)) # 滿足了裝飾器的不修改被裝飾器的源代碼的條件,但是調用方式被修改了 foo(bar) """ 運行結果: in the bar the func run time is 3.0134708881378174 """
- 在不修改被裝飾函數源代碼的情況下為其添加功能
- 返回值中包含函數名
符合裝飾器的條件之二import time def bar(): time.sleep(3) print("in the bar") def foo(func): start_time = time.time() func() stop_time = time.time() print("the func run time is %s" % (stop_time - start_time)) return func # foo(bar()) #print(foo(bar)) # t = foo(bar) # print(t) # t = foo(bar) # t() # 不修改函數的調用方式,為其添加功能 bar = foo(bar) bar()
- 不修改被裝飾函數的調用方式
嵌套函數:
- 在函數體內使用def關鍵字定義一個新的函數
嵌套函數# 嵌套函數 def foo(): print("in the foo") def bar(): print("in the bar") bar() # bar() #報錯,因為bar的作用域僅在foo()函數體內,當foo函數運行結束,那么bar就會釋放空間 foo()
- 變量作用域的訪問順序
代碼演示x = 0 def grandfather(): x = 1 def father(): x = 2 def son(): x = 3 print(x) son() father() grandfather()
閉包:
當局部變量脫離了函數體后,依然可以使用:
def foo(): sum_1 = 100 def deco(): print(sum_1) return deco a = foo() a() #100 # print(sum_1) # 報錯,局部變量不能在局部以外的地方使用
解釋代碼:
def foo(x): def deco(y): print(x+y) return deco a = foo(10) print(a) #<function foo.<locals>.deco at 0x00000275D6421040> a(5) # 15 b = foo(20) print(b) #<function foo.<locals>.deco at 0x0000017D992D10D0> b(5) #25 """ 所有函數都有一個__closure__屬性,如果這個函數是一個閉包的話,那么它返回的是一個由cell對象組成 的元組對象。cell對象的cell_contents屬性就是閉包中的自由變量 """ print(foo.__closure__) #None print(a.__closure__) #(<cell at 0x000002B1F02C1370: int object at 0x000002B1F0116A50>,) print(b.__closure__) #(<cell at 0x000002B1F018A0D0: int object at 0x000002B1F0116B90>,) print(a.__closure__[0].cell_contents) #10 print(b.__closure__[0].cell_contents) #20 """ 這解釋了為什么局部變量脫離函數之后,還可以在函數之外被訪問的原因,因為它存儲在了閉包的cell_contens中了 """
實現裝飾器:
import time def timer(func): def deco(): start_time = time.time() func() stop_time = time.time() print("the func run time is %s" % (stop_time - start_time)) return deco @timer def test1(): time.sleep(3) print("in the test1") @timer def test2(): time.sleep(3) print("in the test2") # test1 = timer(test1) # test2 = timer(test2) test1() test2()
改進后的裝飾器:
import time def timer(func): def 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 @timer def test1(): time.sleep(3) print("in the test1") @timer def test2(name): time.sleep(3) print("in the test2 %s"% name) test1() test2("python")
優化后的裝飾器:
import time def timer(func): def deco(*args, **kwargs): start_time = time.time() res = func(*args, **kwargs) stop_time = time.time() print("the func run time is %s" % (stop_time - start_time)) return res return deco @timer def test1(): time.sleep(1) print("in the test1") @timer def test2(name): time.sleep(2) print("in the test2 %s"% name) @timer def test3(name, age): time.sleep(3) print("in the test3 %s"% name) return age+1 test1() test2("python") #test3("某人飛",999) print(test3("某人飛",999))
加強版裝飾器:
# 加強版裝飾器 usern = "fjf" passwd = "123456" def dl(func,*args, **kwargs): username = input("請輸入您的賬號") password = input("請輸入您的密碼") if username == usern and password == passwd: print("登錄成功") res = func(*args, **kwargs) return res else: print("您的賬號或者密碼錯誤") def login(auth_type): def wrapper_inout(func): def wrapper(*args, **kwargs): if auth_type == "loca": print("通過loca驗證") dl(func, *args, **kwargs) elif auth_type == "loca1": print("通過loca1驗證") dl(func, *args, **kwargs) elif auth_type == "ip": print("通過ip驗證") dl(func, *args, **kwargs) else: print("暫不支持其他登錄方式") return wrapper return wrapper_inout @login(auth_type="loca") def qq_user(): print("歡迎使用qq") @login(auth_type="loca1") def wx_user(name): print("歡迎%s使用微信"%name) @login(auth_type="ip") def wb_user(name, age): print("歡迎%s使用微博"%name) return age >= 18 @login(auth_type="smdx") def ys(): pass ys() qq_user() wx_user("某人飛") print(wb_user("某人飛", 17))
""" 閉包中的拓展知識點: 導入functools模塊: 1.@functools.wraps(func):在閉包中將原函數func的文件名、注釋文檔等,傳遞給inner函數。 2.func.__name__:獲取函數的名字(字符串), 比如:func 這是一個字符串,並不是一個func函數的地址 3.func.__doc__:獲取函數的注釋文檔 """ import functools def auth(func): @functools.wraps(func) # inner.__name=func.__name inner.__doc__=func.__doc__ def inner(*args, **kwargs): """這個是inner函數的注釋文檔""" # 在原函數func執行之前添加的功能 res = func(*args, **kwargs) # 對接收到的參數進行拆包,傳遞給原函數func() # 在原函數func執行之之后添加的功能 return res # 將原函數func的返回值返回給調用者 return inner @auth def func(): """這個是func函數的注釋文檔""" print("this is func") return "func" print(func.__name__) # 原結果:inner 加上代碼 @functools.wraps(func) 后結果為:func print(func.__doc__) # 原結果:這個是inner函數的注釋文檔 加上代碼 @functools.wraps(func) 后結果為:這個是func函數的注釋文檔
