- Python 中的函數(shù)和 Java怔揩、C++不太一樣角撞,Python 中的函數(shù)可以像普通變量一樣當(dāng)做參數(shù)傳遞給另外一個函數(shù)
- 本質(zhì)上憨奸,Python語言的decorator就是一個返回函數(shù)的高階函數(shù)
- Python與Java裝飾器都是讓其他函數(shù)或類在不需要做任何代碼修改的前提下增加額外功能 。但python裝飾器直接實現(xiàn)了AOP剑逃,作用是Java中的注解凝化;Java中AOP要用Spring實現(xiàn)
背景
問題提出
- 夏天的短袖到冬天沒法為我們防風(fēng)御寒
- 我們想到的一個辦法就是把短袖改造一下稍坯,讓它變得更厚更長,這樣一來搓劫,它到冬天還能為我們防風(fēng)御寒(功能增強(qiáng))
- 但問題是瞧哟,這個內(nèi)褲被我們改造成了加厚長袖后混巧,雖然能為我們在冬天防風(fēng)御寒,但本質(zhì)上它不再是一件真正的短袖(改變了函數(shù))
- 我們想不改變短袖的本質(zhì)勤揩,卻還在冬天為我們防風(fēng)御寒
- 于是聰明的人們發(fā)明外套咧党,在不改變短袖的前提下,直接把外套套在了短袖外面雄可。這樣短袖還是短袖凿傅,套上外套后也能為我們在冬天防風(fēng)御寒(不改變函數(shù)缠犀,功能增強(qiáng))
python的函數(shù)
函數(shù)可以像普通變量一樣當(dāng)做參數(shù)傳遞給另外一個函數(shù)
def test():
print("this is what I want")
def receive(func):
print("I receive a function")
func()
receive(test)
python函數(shù)的強(qiáng)大
裝飾器在 Python 使用如此方便都要?dú)w因于 Python 的函數(shù)能像普通的對象一樣能
- 作為參數(shù)傳遞給其他函數(shù)数苫,可以被賦值給其他變量
- 可以作為返回值
- 可以被定義在另外一個函數(shù)內(nèi)。
裝飾器
意義
用于有切面需求的場景辨液,比如:插入日志虐急、性能測試、事務(wù)處理滔迈、緩存止吁、權(quán)限校驗等場景
用裝飾器抽離出大量與函數(shù)功能本身無關(guān)的雷同代碼到裝飾器中并繼續(xù)重用
定義
- 裝飾器本質(zhì)上是一個 Python 函數(shù)或類,它可以讓其他函數(shù)或類在不需要做任何代碼修改的前提下增加額外功能
- 裝飾器的返回值也是一個函數(shù)/類對象
例子
業(yè)務(wù)函數(shù)
def test():
print("this is what I want")
新需求
現(xiàn)在有一個新的需求燎悍,希望可以記錄下函數(shù)的執(zhí)行日志敬惦,于是在代碼中添加日志代碼(新的需求):
def test():
print("call test()") #新需求
print("this is what I want")
如果函數(shù) test()、test2()……
等一大組函數(shù)也有類似的需求谈山,需要在這些函數(shù)里重復(fù)寫新需求俄删,造成問題:
- 修改工作量大
- 造成大量雷同的代碼
- 不利于之后的業(yè)務(wù)擴(kuò)展
新定義業(yè)務(wù)無關(guān)的函數(shù)
- 重新定義一個輔助函數(shù)如
log(func)
專門處理日志(業(yè)務(wù)無關(guān)的代碼,新需求) - 日志處理完之后再執(zhí)行真正的業(yè)務(wù)代碼
test()
def log(func):
print("call test()")
func()
def test():
print("this is what I want")
log(test)
[圖片上傳失敗...(image-fd6eee-1529993035512)]
存在問題
- 上面的修改實現(xiàn)了功能(不改變業(yè)務(wù)函數(shù)奏路,加入新功能)
- 但是調(diào)用的時候不再是調(diào)用真正的業(yè)務(wù)函數(shù)
test()
畴椰,而是調(diào)用輔助函數(shù)log()
這破壞了原有的代碼結(jié)構(gòu), 每次調(diào)用都要把原來的業(yè)務(wù)函數(shù)test()
作為參數(shù)傳遞給輔助函數(shù)log()
簡單裝飾器
想要調(diào)用業(yè)務(wù)函數(shù)而不是 把業(yè)務(wù)函數(shù)作為參數(shù)傳給輔助函數(shù)鸽粉,就要用到裝飾器
def log(func):
def wrapper():
print("call test()")
return func() # 執(zhí)行傳入的函數(shù)參數(shù)斜脂,如test()
return wrapper #返回函數(shù)對象 wrapper
def test():
print("this is what I want")
test= log(test)
# 因為裝飾器 use_logging(test) 返回的是函數(shù)對象 wrapper
# 這條語句相當(dāng)于 test = wrapper
test() # 執(zhí)行test() 相當(dāng)于執(zhí)行 wrapper()
輔助函數(shù)log()
就是一個裝飾器,它把執(zhí)行真正業(yè)務(wù)函數(shù) func 包裹在其中触机,看起來像 test()
被log()
裝飾了一樣帚戳。
這么做可以實現(xiàn)面向切面編程AOP。
裝飾器進(jìn)階
一. @ 語法糖
- @ 符號是裝飾器的語法糖儡首,它放在函數(shù)開始定義的地方
- 這樣就可以省略最后一步再次賦值的操作
本例子中@use_logging
相當(dāng)于test= use_logging(test)
def log(func):
def wrapper():
print("call test()")
return func()
return wrapper
@log
def test():
print("this is what I want")
test()
- 如此一來片任,業(yè)務(wù)函數(shù)
test()
不需要做任何修改,只需在定義的地方加上裝飾器椒舵,調(diào)用的時候不用加任何修飾 - 如果我們有其他的類似函數(shù)蚂踊,我們可以繼續(xù)調(diào)用裝飾器如
@log
來修飾函數(shù),而不用重復(fù)修改函數(shù)或者增加新的封裝
這樣笔宿,我們就提高了程序的可重復(fù)利用性犁钟,并增加了程序的可讀性棱诱。
二. 帶參數(shù)的業(yè)務(wù)函數(shù)
*args、**kwargs用法
*args
和**kwargs
是python中的可變參數(shù)涝动。
def foo(*args, **kwargs):
print 'args = ', args
print 'kwargs = ', kwargs
print '---------------------------------------'
if __name__ == '__main__':
foo(1,2,3,4)
foo(a=1,b=2,c=3)
foo(1,2,3,4, a=1,b=2,c=3)
foo('a', 1, None, a=1, b='2', c=3)
輸出結(jié)果:
-
*args
表示任何多個無名參數(shù)迈勋,它是一個tuple -
**kwargs
表示關(guān)鍵字參數(shù),它是一個dict - 同時使用
*args
和**kwargs
時醋粟,必須*args
參數(shù)列要在**kwargs
前
有限個參數(shù)
當(dāng)業(yè)務(wù)函數(shù)test()
需要參數(shù)靡菇,比如 test(a,b)
:
def test(a,b):
print("a+b = %d" % (a+b))
我們可以在定義 wrapper()
函數(shù)的時候指定參數(shù):
def log(func):
def wrapper(a,b):
print("call test(%d,%d)" %(a,b))
return func(a,b)
return wrapper
這樣業(yè)務(wù)函數(shù)test(name)
定義的參數(shù)就可以定義在wrapper ()
函數(shù)中米愿。
總的代碼:
# -*- coding: UTF-8 -*-
def log(func):
def wrapper(a,b):
print("call test(%d厦凤,%d)" %(a,b))
return func(a,b)
return wrapper
@log
def test(a,b):
print("sum = %d" % (a+b))
test(2,4)
可變參數(shù)
當(dāng)裝飾器不知道業(yè)務(wù)函數(shù)到底有多少個參數(shù)時,用*args
來代替育苟,
如此一來较鼓,不管業(yè)務(wù)函數(shù) test(…)
定義了多少個參數(shù),都可以完整地傳遞到 func
中去
def log(func):
def wrapper(*args):
print("call test()" )
return func(*args)
return wrapper
@log
def test(a,b,c):
print("sum = %d" % (a+b+c))
test(2,4,5)
[圖片上傳失敗...(image-82a649-1529993035512)]
含關(guān)鍵字的可變參數(shù)
如果業(yè)務(wù)函數(shù) test(…)
還定義了一些關(guān)鍵字參數(shù)违柏,用**kwargs
來代替:
def test(a,b,c,way=None):
print("sum = %d,way = %s" % ((a+b+c),way))
可以把 wrapper()
函數(shù)指定關(guān)鍵字函數(shù):
def log(func):
def wrapper(*args, **kwargs):
print("call test()" )
return func(*args, **kwargs)
return wrapper
總的:
def log(func):
def wrapper(*args, **kwargs):
print("call test()" )
return func(*args, **kwargs)
return wrapper
@log
def test(a,b,c,way=None):
print("sum = %d,way = %s" % ((a+b+c),way))
test(2,4,5,"add")
三. 帶參數(shù)的裝飾器
裝飾器還有更大的靈活性博烂,例如帶參數(shù)的裝飾器,在上面的裝飾器調(diào)用中漱竖,該裝飾器接收唯一的參數(shù)就是業(yè)務(wù)函數(shù) test()
裝飾器的語法允許我們在調(diào)用時禽篱,提供其它參數(shù),比如@decorator(a)
馍惹。這樣躺率,就為裝飾器的編寫和使用提供了更大的靈活性。比如讼积,我們可以在裝飾器中指定日志的等級肥照,因為不同業(yè)務(wù)函數(shù)可能需要的日志級別是不一樣的。
def log(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
print("%s waring" % func.__name__)
elif level == "info":
print("%s infomation get" % func.__name__)
return func(*args, **kwargs)
return wrapper
return decorator
@log(level="warn")
def test():
print("this is what I want")
@log(level="info")
def test2():
print("this is also what I want")
test()
test2()
上面的 log()
是允許帶參數(shù)的裝飾器勤众。它實際上是對原有裝飾器的一個函數(shù)封裝舆绎,并返回一個裝飾器。我們可以將它理解為一個含有參數(shù)的閉包们颜。當(dāng)我們用@log(level="warn")
調(diào)用的時候吕朵,Python 能夠發(fā)現(xiàn)這一層的封裝,并把參數(shù)傳遞到裝飾器的環(huán)境中窥突。
四. 類裝飾器
定義
- 裝飾器不僅可以是函數(shù)努溃,還可以是類
- 相比函數(shù)裝飾器,類裝飾器具有靈活度大阻问、高內(nèi)聚梧税、封裝性等優(yōu)點(diǎn)
- 像
__call__
這樣前后都帶下劃線的方法在Python中被稱為內(nèi)置(魔法)方法。重載這些魔法方法一般會改變對象的內(nèi)部行為
用法
- 讓類的構(gòu)造函數(shù)
__init__()
接受一個函數(shù) - 重載
__call__()
并 返回一個函數(shù) - 使用
@類
形式將裝飾器附加到業(yè)務(wù)函數(shù)上
class DecorateDemo(object):
def __init__(self, func):
self.__func = func
def __call__(self):
print("before class decorator")
self.__func()
print("after class decorator")
return self.__func
@DecorateDemo
def test():
print("this is what I want")
test()
五. functools模塊
functools模塊提供了兩個裝飾器,主要用 functools.wraps
使用裝飾器極大地復(fù)用了代碼第队,但是他有一個缺點(diǎn)就是原函數(shù)的元信息不見了:
def log(func):
def wrapper():
print("call test()")
return func()
return wrapper
@log
def test():
print("this is what I want")
test()
print test.__name__
import functools
def log(func):
@functools.wraps(func)
def wrapper():
print("call test()")
return func()
return wrapper
@log
def test():
print("this is what I want")
test()
print test.__name__
六. 多個裝飾器
裝飾器順序
一個函數(shù)還可以同時定義多個裝飾器哮塞,比如:
@a
@b
@c
def f ():
pass
它的執(zhí)行順序是從里到外,最先調(diào)用最里層的裝飾器凳谦,最后調(diào)用最外層的裝飾器忆畅,它等效于
f = a(b(c(f)))
例子:
def log(func):
def wrapper():
print("call test()")
result = func()
print("end test()")
return result
return wrapper
def hello(func):
def wrapper():
print("hello test()")
result = func()
print("goodbye test()")
return result
return wrapper
@log
@hello
def test():
print("this is what I want")
test()