簡介
裝飾器是可呼叫的物件,其引數是另一個函式(被裝飾的函式)。 python的世界中,一切皆物件,把函式看做一個物件,一個可以拼接、編輯的物件。執行物件的方法是__ 物件()__
裝飾器只是python的語法糖
觀察下面的兩個呼叫,他們的效果是一樣的。
#!/usr/bin/env python
#-*-coding:utf-8-*-
'''
@file: dec4.py
@time: 2018/12/13 17:15
'''
import functools
def register(func):
@functools.wraps(func)
def warpper(*args,**kwargs):
print("啦啦啦,這裡裝飾函式 %s"%func.__name__)
result = func() #這裡就是真正的執行邏輯
return result
return warpper # 返回包裝後的函式
@register
def f1():
print("使用@語法的裝飾器 %s"%f1.__name__)
def f2():
print("沒@語法的裝飾器 %s" % f2.__name__)
if __name__ == '__main__':
f1()
register(f2)()
#或者
# f_ = register(f2)
# f_()
複製程式碼
裝飾器啟動裝飾的時機
1、檔案匯入時裝飾器已經啟動對函式的裝飾。
裝飾器的一個關鍵特性是,它們在被裝飾的函式定義之後立即執行。 這通常是在匯入時(即 Python 載入模組時)。--cookbook
建立檔案 dec1.py
# dec1.py
registry = [] # 存放註冊函式
def register(func):
print('裝飾器入列表(%s)' % func.__name__)
registry.append(func)
return func
@register
def f1():
print('執行 %s'%f1.__name__)
@register
def f2():
print('執行 %s' % f2.__name__)
複製程式碼
建立檔案 dec2.py
# dec2.py
from 你的目錄 import dec1
複製程式碼
執行檔案 dec2.py 輸出如下:
裝飾器返回的結果
1、返回目標函式。 2、返回目標函式的結果。
以上 register的用法適用於對函式進行預處理的場景,例如flask框架的路由註冊,在flask app啟動時將所有的函式收集起來,與註冊的路徑進行一一對應,但是函式執行時跟原函式沒有任何區別,因為裝飾器返回的還是目標函式。假如我們需要在函式執行時進行日誌的輸出,則需要將函式進行一層封裝,即將目標函式包裝成另一個函式,這時返回的是另一個函式,只是包裝後的函式可以進行額外的操作。
以下是兩個裝飾器的寫法,注意區別。
import functools
def register1(func): #在檔案引入時返回目標函式的裝飾器
#這裡進行一些函式預處理的操作,例如將函式名字收集進一個列表
#list.append(func.__name__)
return func
def register2(func): #在函式執行時返回函式的裝飾器
@functools.wraps(func)
def warpper(*args,**kwargs):
print("函式沒有執行 %s"%func.__name__)
result = func #這裡就是真正的執行邏輯 #注意-----這裡沒有執行函式
return result
return warpper # 返回包裝後的函式
def register3(func): #返回目標函式結果的裝飾器
@functools.wraps(func)
def warpper(*args,**kwargs):
print("函式開始執行 %s"%func.__name__)
result = func() #這裡就是真正的執行邏輯
return result
return warpper # 返回包裝後的函式
@register1
def f1():
print("執行函式 %s"%f1.__name__)
@register2
def f2():
print("執行函式 %s"%f2.__name__)
@register3
def f3():
print("執行函式 %s"%f3.__name__)
if __name__ == '__main__':
f1()
f2()
f3()
複製程式碼
執行結果:
這時新增程式碼:
if __name__ == '__main__':
f1()
f2()
f_ = f2()
print("開始真正執行f2函式啦")
f_()
f3()
複製程式碼
執行結果:
裝飾函式的屬性保持
1、使用@functools.wraps
你會發現下面的程式碼中,如果wrapper函式沒有使用@functools.wraps裝飾的話,f1.__name__返回的是wrapper函式的名字,這是因為我們返回的函式已經是wrapper這個函式,所以__name__屬性也就不是原來函式的了,@functools.wraps裝飾器的作用就是將目標函式的屬性例如__name__等原封不動轉移給包裝好的函式。
#!/usr/bin/env python
#-*-coding:utf-8-*-
'''
@file: dec4.py
@time: 2018/12/13 17:15
'''
import functools
def register(func): #返回目標函式結果的裝飾器
def warpper(*args,**kwargs):
result = func() #這裡就是真正的執行邏輯
return result
return warpper # 返回包裝後的函式
def register2(func): #返回目標函式結果的裝飾器
@functools.wraps(func)
def warpper(*args,**kwargs):
result = func() #這裡就是真正的執行邏輯
return result
return warpper # 返回包裝後的函式
@register
def f1():
print("函式名為 %s"%f1.__name__)
@register2
def f2():
print("函式名為 %s" % f2.__name__)
if __name__ == '__main__':
f1()
f2()
#或者
#f_ = register(f2)
#f_()
複製程式碼
執行結果:
帶引數的裝飾器
1、外層巢狀一層函式傳參,返回一個裝飾器。
裝飾器也是一個物件,只要使用一個函式封裝裝飾器,返回目標裝飾器即可,這時就可以通過外層的函式傳進引數。
#!/usr/bin/env python
# -*-coding:utf-8-*-
'''
@file: dec5.py
@time: 2018/12/13 18:13
'''
import functools
def dec_arg(arg=None):
def register(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('傳進的引數為 %s' % arg)
return func(*args, **kwargs)
return wrapper
return register #這是裝飾器
@dec_arg(arg='aaaa')
def f1():
print("執行 %s"%f1.__name__)
def f2():
print("執行 %s" % f2.__name__)
if __name__ == '__main__':
f1()
f2_ = dec_arg(arg='bbbb')(f2)
f2_()
dec_arg(arg='cccc')(f2)()
複製程式碼