裝飾器(Decorators)是 Python 的一個重要部分。簡單地說:他們是修改其他函數(shù)的功能的函數(shù)。他們有助于讓我們的代碼更簡短,也更Pythonic(Python范兒)。在程序開發(fā)中經(jīng)常使用到的功能,合理使用裝飾器,能讓我們的程序如虎添翼。
私信小編01 獲取全套學(xué)習(xí)教程!
1. 函數(shù)名應(yīng)用
函數(shù)名是什么?函數(shù)名是函數(shù)的名字,本質(zhì):變量,特殊的變量。
1 ) 函數(shù)名就是函數(shù)的內(nèi)存地址,直接打印函數(shù)名,就是打印內(nèi)存地址
def func1(): print(123) print(func1) # <function func1 at 0x0000029042E02E18>
2 ) 函數(shù)名可以作為變量
def func1(): print(111) f = func1 f() # f() 就是func1()
3 ) 函數(shù)名可以作為函數(shù)的參數(shù)
def func1(): print(111) def func2(x): x() func2(func1) #func1作為func2的參數(shù)
4 ) 函數(shù)名可以作為函數(shù)的返回值
def wrApper():
def inner():
print('inner')
return inner
f = wrapper()
f()
5 ) 函數(shù)名可以作為容器類類型的元素
使用for循環(huán)批量執(zhí)行函數(shù)
def func1():
print('func1')
def func2():
print('func2')
def func3():
print('func3')
l1 = [func1,func2,func3]
for i in l1:
i()
像上面函數(shù)名這種,叫做第一類對象。
第一類對象( first-class object)指:
1.可在運行期創(chuàng)建
2.可用作函數(shù)參數(shù)或返回值
3.可存入變量的實體
*不明白?那就記住一句話,就當(dāng)普通變量用
2. 閉包
1、 閉包函數(shù) : 內(nèi)部函數(shù)包含對外部作用域而非全局作用域變量的引用,該內(nèi)部函數(shù)稱為閉包函數(shù)
2、閉包的作用:爬蟲、裝飾器
當(dāng)程序執(zhí)行遇到函數(shù)執(zhí)行時,會在內(nèi)存空間開辟局部命名空間,當(dāng)函數(shù)執(zhí)行完畢,該命名空間會被銷毀。但是如果這個函數(shù)內(nèi)部形成閉包,則該內(nèi)存空間不會隨著函數(shù)執(zhí)行完而消失。
3、如何判斷是否是閉包:print(函數(shù)名.__closure__) 結(jié)果是 cell 說明是閉包,結(jié)果是 None說明不是閉包。
閉包舉例
def wrapper(): name = 'summer' def inner(): print(name) inner() wrapper() # summer
如何判斷它是否是一個閉包函數(shù)呢? 內(nèi)層函數(shù)名.__closure__ cell 就是=閉包
例 1.
def wrapper(): name = 'summer' def inner(): print(name) inner() print(inner.__closure__) wrapper() 執(zhí)行輸出: summer (<cell at 0x0000017FC9C90B58: str object at 0x0000017FCA349AD0>,)
例 2.
name = 'summer' def wrapper(): def inner(): print(name) inner() print(inner.__closure__) wrapper() 結(jié)果輸出: summer None
返回值為None 表示它不是閉包,因為name是一個全局變量,如果函數(shù)調(diào)用了外層變量而非全局變量,那么它就是閉包。
例 3.
name = 'summer' def wrapper2(): name1 = 'spring' def inner(): print(name) print(name1) inner() print(inner.__closure__) wrapper2() 結(jié)果輸出: summer spring (<cell at 0x030B7310: str object at 0x03043680>,)
只要引用了外層變量至少一次,非全局的,它就是閉包
例 4:判斷 下面的函數(shù),是一個閉包嗎? ******
name = 'summer' def wraaper2(n): #相當(dāng)于n = 'summer' def inner(): print(n) inner() print(inner.__closure__) wraaper2(name) 結(jié)果輸出: summer (<cell at 0x03867350: str object at 0x037F3680>,)
它也是一個閉包. 雖然wraaper2傳了一個全局變量,但是在函數(shù)wraaper2內(nèi)部,inner引用了外層變量,相當(dāng)于在函數(shù)inner外層定義了 n = 'summer',所以inner是一個閉包函數(shù)
閉包的好處 : 當(dāng)函數(shù)開始執(zhí)行時,如果遇到了閉包,他有一個機制,他會永遠開辟一個內(nèi)存空間,將閉包中的變量等值放入其中,不會隨著函數(shù)的執(zhí)行完畢而消失。
舉一個例子:爬3次,內(nèi)存開了3次,很占用內(nèi)存
from urllib.request import urlopen
content1 = urlopen('https://www.cnblogs.com/').read().decode('utf-8')
content2 = urlopen('https://www.cnblogs.com/').read().decode('utf-8')
content3 = urlopen('https://www.cnblogs.com/').read().decode('utf-8')
把它封裝成閉包
from urllib.request import urlopen def index(): url = "https://www.cnblogs.com/" def get(): return urlopen(url).read() return get #return的是get,就是一個函數(shù)名 cnblog = index() print(cnblog) # <function index.<locals>.get at 0x02F46978> content = cnblog() print(content) # 頁面源碼
這個例子,只有第一遍,是從網(wǎng)站抓取的。之后的執(zhí)行,直接從內(nèi)存中加載,節(jié)省內(nèi)存空間
3.裝飾器
3.1 裝飾器初識
裝飾器本質(zhì): 就是一個python函數(shù),他可以讓其他函數(shù)在不需要做任何代碼變動的前提下,增加額外的功能,裝飾器的返回值也是一個函數(shù)對象。
裝飾器的應(yīng)用場景:比如插入日志,性能測試,事務(wù)處理,緩存等等場景。
import time
def timmer(f):
def inner():
start_time = time.time()
f()
end_time = time.time()
print('此函數(shù)的執(zhí)行時間為{}'.format(end_time - start_time))
return inner
def func1():
print('in func1')
time.sleep(1)
func1 = timmer(func1)
print(func1)
func1() # 這里的func1是全新的func1,就是上面的賦值,此時相當(dāng)于執(zhí)行 inner函數(shù)
輸出結(jié)果:
<function timmer.<locals>.inner at 0x03822DF8>
in func1
此函數(shù)的執(zhí)行時間為1.0003533363342285
代碼從上至下執(zhí)行
語法糖: 想測試誰,前面加@裝飾器函數(shù),即可。 寫裝飾器,約定俗成,函數(shù)名為wrapper
def wrapper(func):
def inner(*args,**kwargs):
'''被裝飾函數(shù)之前'''
ret = func(*args,**kwargs)
'''被裝飾函數(shù)之后'''
return ret
return inner
@wrapper
def func(*args,**kwargs):
print(args,kwargs)
return 666
print(func())
輸出結(jié)果:
() {}
666
裝飾器利用return制造了一個假象,func()執(zhí)行,其實是執(zhí)行inner() , func()把原來的func()給覆蓋了
3.2 裝飾器傳參
例 1: 上面裝飾器的例子,func1,要傳2個參數(shù)a,b
import time
def timmer(f):
def inner(a,b):
start_time = time.time()
f(a,b)
end_time = time.time()
print('此函數(shù)的執(zhí)行時間為{}'.format(end_time - start_time))
return inner
@timmer
def func1(a,b):
print('in func1 {}{}'.format(a,b))
time.sleep(1) # 模擬程序邏輯
func1(1,2)
執(zhí)行輸出:
in func1 12
此函數(shù)的執(zhí)行時間為1.0006024837493896
例 2: 如果有多個參數(shù)呢?改成動態(tài)參數(shù)
import time
def timmer(f):
def inner(*args,**kwargs):
start_time = time.time()
f(*args,**kwargs)
end_time = time.time()
print('此函數(shù)的執(zhí)行時間為{}'.format(end_time - start_time))
return inner
@timmer
def func1(*args,**kwargs):
print('in func1 {}{}'.format(args,kwargs))
time.sleep(1) # 模擬程序邏輯
func1(1,2,a='3',b=4)
執(zhí)行輸出:
in func1 (1, 2){'b': 4, 'a': '3'}
此函數(shù)的執(zhí)行時間為1.000101089477539
函數(shù)的執(zhí)行時,*打散 ;
函數(shù)的定義時,*聚合。
from functools import wraps def wrapper(f): # f = func1 def inner(*args,**kwargs): #聚合,args (1,2,3) '''執(zhí)行函數(shù)之前的相關(guān)操作''' ret = f(*args,**kwargs) # 打散 1,2,3 '''執(zhí)行函數(shù)之后的相關(guān)操作''' return ret return inner @wrapper # func1 = wrapper(func1) func1 = inner def func1(*args): #args (1,2,3) 聚合 print(666) return args print(func1(*[1,2,3])) 執(zhí)行輸出: 666 (1, 2, 3)
例 3 *****
import time #1.加載模塊
def timmer(*args,**kwargs): #2.加載變量 5.接收參數(shù)True,2,3
def wrapper(f): #6.加載變量 8.f = func1
print(args, kwargs) #9.接收timmer函數(shù)的值True,2,3
def inner(*args,**kwargs): #10.加載變量. 13.執(zhí)行函數(shù)inner
if flag: #14 flag = True
start_time = time.time() #15 獲取當(dāng)前時間
ret = f(*args,**kwargs) #16 執(zhí)行func1
time.sleep(0.3) #19 等待0.3秒
end_time = time.time() #20 獲取當(dāng)前時間
print('此函數(shù)的執(zhí)行效率%f' % (end_time-start_time)) #21 打印差值
else:
ret = f(*args, **kwargs)
return ret #22 返回給函數(shù)調(diào)用者func1()
return inner #11 返回給函數(shù)調(diào)用者wrapper
return wrapper #7.返回給函數(shù)調(diào)用timmer(flag,2,3)
flag = True #3 加載變量
@timmer(flag,2,3) # 4.執(zhí)行函數(shù)timmer(flag,2,3) 17.執(zhí)行函數(shù)func1 兩步:1,timmer(flag,2,3) 相當(dāng)于執(zhí)行wrapper 2.@wrapper 裝飾器 func1 = wrapper(func1)
def func1(*args,**kwargs):
return 666 #18 返回給函數(shù)調(diào)用者f(*args,**kwargs)
print(func1()) #12 執(zhí)行函數(shù)
寫裝飾器,一般嵌套3層就可以了
3.3 多個裝飾器,裝飾一個函數(shù)
def wrapper1(func): # func == f函數(shù)名
def inner1():
print('wrapper1 ,before func') # 2
func()
print('wrapper1 ,after func') # 4
return inner1
def wrapper2(func): # func == inner1
def inner2():
print('wrapper2 ,before func') # 1
func()
print('wrapper2 ,after func') # 5
return inner2
@wrapper2 # f = wrapper2(f) 里面的f==inner1 外面的f == inner2
@wrapper1 # f = wrapper1(f) 里面的f==函數(shù)名f 外面的f == inner1
def f(): # 3
print('in f')
f() # inner2()
執(zhí)行輸出:
wrapper2 ,before func
wrapper1 ,before func
in f
wrapper1 ,after func
wrapper2 ,after func
哪個離函數(shù)近,哪個先計算 。 最底下的先執(zhí)行
執(zhí)行順序如下圖:
多個裝飾器,都是按照上圖的順序來的
4. 裝飾器的 __name__ 和 __doc___
__name__:函數(shù)名
__doc___:函數(shù)的解釋
普通函數(shù)
def func1(): """ 此函數(shù)是完成登陸的功能,參數(shù)分別是...作用。 return: 返回值是登陸成功與否(True,F(xiàn)alse) """ print(666) func1() print(func1.__name__) #獲取函數(shù)名 print(func1.__doc__) #獲取函數(shù)名注釋說明 執(zhí)行輸出: 666 func1 此函數(shù)是完成登陸的功能,參數(shù)分別是...作用。 return: 返回值是登陸成功與否(True,F(xiàn)alse)
這個有什么用呢?比如日志功能,需要打印出誰在什么時間,調(diào)用了什么函數(shù),函數(shù)是干啥的,花費了多次時間,這個時候,就需要獲取函數(shù)的有用信息了
帶裝飾器的函數(shù)
def wrapper(f): # f = func1 def inner(*args,**kwargs): #聚合, args (1,2,3) '''執(zhí)行函數(shù)之前的相關(guān)操作''' ret = f(*args,**kwargs) # 打散 1,2,3 '''執(zhí)行函數(shù)之后的相關(guān)操作''' return ret return inner @wrapper def func1(): """ 此函數(shù)是完成登陸的功能,參數(shù)分別是...作用。 return: 返回值是登陸成功與否(True,F(xiàn)alse) """ print(666) return True func1() print(func1.__name__) print(func1.__doc__) 執(zhí)行輸出: 666 inner 執(zhí)行函數(shù)之前的相關(guān)操作
函數(shù)裝飾之后,相當(dāng)于執(zhí)行了inner函數(shù),所以輸出inner
為了解決這個問題,需要 調(diào)用一個模塊wraps
wraps將 被修飾的函數(shù)(wrapped) 的一些屬性值賦值給修飾器函數(shù)(wrapper) ,最終讓屬性的顯示更符合我們的直覺
from functools import wraps def wrapper(f): # f = func1 @wraps(f) #f是被裝飾的函數(shù) def inner(*args,**kwargs): #聚合args (1,2,3) '''執(zhí)行函數(shù)之前的相關(guān)操作''' ret = f(*args,**kwargs) # 打散 1,2,3 '''執(zhí)行函數(shù)之后的相關(guān)操作''' return ret return inner @wrapper def func1(): """ 此函數(shù)是完成登陸的功能,參數(shù)分別是...作用。 return: 返回值是登陸成功與否(True,F(xiàn)alse) """ print(666) return True func1() print(func1.__name__) print(func1.__doc__) 執(zhí)行輸出: 666 func1 此函數(shù)是完成登陸的功能,參數(shù)分別是...作用。 return: 返回值是登陸成功與否(True,F(xiàn)alse)






