- 裝飾器是程序開發(fā)中經(jīng)常會用到的一個(gè)功能匈子,用好了裝飾器,開發(fā)效率如虎添翼讳嘱,所以這也是Python面試中必問的問題幔嗦,但對于好多初次接觸這個(gè)知識的人來講,這個(gè)功能有點(diǎn)繞沥潭,自學(xué)時(shí)直接繞過去了邀泉,然后面試問到了就掛了,因?yàn)檠b飾器是程序開發(fā)的基礎(chǔ)知識钝鸽。
裝飾器(decorator)功能
- 引入日志
- 函數(shù)執(zhí)行時(shí)間統(tǒng)計(jì)
- 執(zhí)行函數(shù)前預(yù)備處理
- 執(zhí)行函數(shù)后清理功能
- 權(quán)限校驗(yàn)等場景
- 緩存
個(gè)人理解+示例
import time
"""
裝飾器理解:
前提:
定義一個(gè)函數(shù) def foo()
foo是函數(shù)的引用
foo()是執(zhí)行這個(gè)函數(shù)
定義一個(gè)閉包c(diǎn)alTime(func)呼渣,把函數(shù)的引用的傳到閉包中,保留該函數(shù)寞埠,并可以通過()調(diào)用函數(shù)
通過@calTime 使test = callTime(test)屁置,新的函數(shù)中有之前函數(shù)的引用(因?yàn)殚]包的特性),
并且 在沒有調(diào)用函數(shù)之前就已經(jīng)裝填仁连。
這樣就可以在執(zhí)行之前的函數(shù)前蓝角,做一些操作。
裝飾器的實(shí)質(zhì)是創(chuàng)建了一個(gè)新的函數(shù)饭冬,只是引用名是相同的使鹅,實(shí)際是不同的兩個(gè)函數(shù)
執(zhí)行新的函數(shù),實(shí)際是執(zhí)行閉包中的函數(shù)昌抠。
"""
# 不帶參數(shù)的裝飾器
def calTime(func):
def cal():
start_time = time.time()
func()
stop_time = time.time()
print("執(zhí)行時(shí)間 %f" % (stop_time - start_time))
return cal
@calTime # 相當(dāng)于test = callTime(test) 在沒有調(diào)用函數(shù)之前就已經(jīng)裝填
def test():
for i in range(100000):
pass
test()
# 帶參數(shù)的裝飾器
def set_func(func):
def call_func(a):
print("驗(yàn)證權(quán)限----->1")
func(a)
return call_func
@set_func
def test_num(num):
print("打印參數(shù):%d" % num)
test_num(10)
# 不定長參數(shù)的裝飾器
def set_func(func):
def call_func(*args, **kwargs):
print("驗(yàn)證權(quán)限----->1")
# func(args,kwargs) 不能這么寫患朱,相當(dāng)于傳遞了兩個(gè)參數(shù):一個(gè)元組,一個(gè)字典
func(*args, **kwargs) # * ** 的作用是拆包炊苫,拆成一個(gè)一個(gè)的數(shù)據(jù)
return call_func
@set_func
def test_num(num, *args, **kwargs):
print("打印參數(shù):%d" % num)
print("打印參數(shù):", args)
print("打印參數(shù):", kwargs)
test_num(10)
test_num(10, 20)
test_num(10, 20, 30, k=3)
# 有返回值的裝飾器裁厅。裝飾沒有返回值的函數(shù)時(shí)冰沙,返回None,不會出現(xiàn)問題执虹。
# 通用的裝飾器
def set_func(func):
def call_func(*args, **kwargs):
print("驗(yàn)證權(quán)限----->1")
# func(args,kwargs) 不能這么寫拓挥,相當(dāng)于傳遞了兩個(gè)參數(shù):一個(gè)元組,一個(gè)字典
return func(*args, **kwargs) # * ** 的作用是拆包袋励,拆成一個(gè)一個(gè)的數(shù)據(jù)
return call_func
@set_func
def test_num(num, *args, **kwargs):
print("打印參數(shù):%d" % num)
print("打印參數(shù):", args)
print("打印參數(shù):", kwargs)
return "ok"
ret = test_num(10)
print(ret)
# 多個(gè)裝飾器裝飾一個(gè)函數(shù)
def add_qx(func):
print("開始裝飾權(quán)限")
def call_func(*args, **kwargs):
print("增加權(quán)限")
# func(args,kwargs) 不能這么寫侥啤,相當(dāng)于傳遞了兩個(gè)參數(shù):一個(gè)元組,一個(gè)字典
return func(*args, **kwargs) # * ** 的作用是拆包茬故,拆成一個(gè)一個(gè)的數(shù)據(jù)
return call_func
def add_xx(func):
print("開始裝飾功能")
def call_func(*args, **kwargs):
print("增加功能")
# func(args,kwargs) 不能這么寫盖灸,相當(dāng)于傳遞了兩個(gè)參數(shù):一個(gè)元組,一個(gè)字典
return func(*args, **kwargs) # * ** 的作用是拆包磺芭,拆成一個(gè)一個(gè)的數(shù)據(jù)
return call_func
# 裝填順序add_xx add_qx 先裝填距離函數(shù)最近的
# 執(zhí)行順序add_qx add_xx 最后裝填的先執(zhí)行糠雨,因?yàn)樽詈蟮闹赶蚴撬缓笠粚诱{(diào)用回去
@add_qx
@add_xx
def test_num(num, *args, **kwargs):
print("打印參數(shù):%d" % num)
return "ok"
ret = test_num(10)
print(ret)
# 練習(xí) 實(shí)現(xiàn) <h1>haha</h1>
def add_h1(func):
def call_h1():
return "<h1>" + func() + "</h1>"
return call_h1
@add_h1
def test_string():
return "haha"
print(test_string())
1徘跪、先明白這段代碼
#### 第一波 ####
def foo():
print('foo')
foo # 表示是函數(shù)
foo() # 表示執(zhí)行foo函數(shù)
#### 第二波 ####
def foo():
print('foo')
foo = lambda x: x + 1
foo() # 執(zhí)行l(wèi)ambda表達(dá)式,而不再是原來的foo函數(shù)琅攘,因?yàn)閒oo這個(gè)名字被重新指向了另外一個(gè)匿名函數(shù)
函數(shù)名僅僅是個(gè)變量垮庐,只不過指向了定義的函數(shù)而已,所以才能通過 函數(shù)名()調(diào)用坞琴,如果 函數(shù)名=xxx被修改了哨查,那么當(dāng)在執(zhí)行 函數(shù)名()時(shí),調(diào)用的就不知之前的那個(gè)函數(shù)了
2剧辐、使用原因
寫代碼要遵循開放封閉
原則寒亥,雖然在這個(gè)原則是用的面向?qū)ο箝_發(fā),但是也適用于函數(shù)式編程荧关,簡單來說溉奕,它規(guī)定已經(jīng)實(shí)現(xiàn)的功能代碼不允許被修改,但可以被擴(kuò)展忍啤,即:
- 封閉:已實(shí)現(xiàn)的功能代碼塊
- 開放:對擴(kuò)展開發(fā)
如果將開放封閉原則應(yīng)用在上述需求中加勤,那么就不允許在函數(shù) f1 、f2同波、f3鳄梅、f4的內(nèi)部進(jìn)行修改代碼,于是就有了下面的實(shí)現(xiàn)方案:
def w1(func):
def inner():
# 驗(yàn)證1
# 驗(yàn)證2
# 驗(yàn)證3
func()
return inner
@w1
def f1():
print('f1')
@w1
def f2():
print('f2')
@w1
def f3():
print('f3')
@w1
def f4():
print('f4')
對于上述代碼未檩,也是僅僅對基礎(chǔ)平臺的代碼進(jìn)行修改戴尸,就可以實(shí)現(xiàn)在其他人調(diào)用函數(shù) f1 f2 f3 f4 之前都進(jìn)行【驗(yàn)證】操作,并且無需做任何操作冤狡。
詳解:
單獨(dú)以f1為例:
def w1(func):
def inner():
# 驗(yàn)證1
# 驗(yàn)證2
# 驗(yàn)證3
func()
return inner
@w1
def f1():
print('f1')
python解釋器就會從上到下解釋代碼孙蒙,步驟如下:
- def w1(func): ==>將w1函數(shù)加載到內(nèi)存
- @w1
沒錯(cuò)项棠, 從表面上看解釋器僅僅會解釋這兩句代碼,因?yàn)楹瘮?shù)在 沒有被調(diào)用之前其內(nèi)部代碼不會被執(zhí)行马篮。
從表面上看解釋器著實(shí)會執(zhí)行這兩句沾乘,但是 @w1 這一句代碼里卻有大文章, @函數(shù)名 是python的一種語法糖浑测。
上例@w1內(nèi)部會執(zhí)行一下操作:
執(zhí)行w1函數(shù)
執(zhí)行w1函數(shù) 黄痪,并將 @w1 下面的函數(shù)作為w1函數(shù)的參數(shù)俄精,即:**@w1 等價(jià)于 w1(f1) **所以,內(nèi)部就會去執(zhí)行:
def inner(): #驗(yàn)證 1 #驗(yàn)證 2 #驗(yàn)證 3 f1() # func是參數(shù),此時(shí) func 等于 f1 return inner# 返回的 inner烹卒,inner代表的是函數(shù),非執(zhí)行函數(shù) ,其實(shí)就是將原來的 f1 函數(shù)塞進(jìn)另外一個(gè)函數(shù)中
w1的返回值
將執(zhí)行完的w1函數(shù)返回值 賦值 給@w1下面的函數(shù)的函數(shù)名f1 即將w1的返回值再重新賦值給 f1盾计,即:
新f1 = def inner(): #驗(yàn)證 1 #驗(yàn)證 2 #驗(yàn)證 3 原來f1() return inner
所以搞糕,以后業(yè)務(wù)部門想要執(zhí)行 f1 函數(shù)時(shí),就會執(zhí)行 新f1 函數(shù)蜂科,在新f1 函數(shù)內(nèi)部先執(zhí)行驗(yàn)證顽决,再執(zhí)行原來的f1函數(shù),然后將原來f1 函數(shù)的返回值返回給了業(yè)務(wù)調(diào)用者导匣。
如此一來才菠, 即執(zhí)行了驗(yàn)證的功能,又執(zhí)行了原來f1函數(shù)的內(nèi)容贡定,并將原f1函數(shù)返回值 返回給業(yè)務(wù)調(diào)用著
Low BBB 你明白了嗎赋访?要是沒明白的話,我晚上去你家?guī)湍憬鉀Q吧;捍r镜ⅰ!
3. 再議裝飾器
# 定義函數(shù):完成包裹數(shù)據(jù)
def makeBold(fn):
def wrapped():
return "<b>" + fn() + "</b>"
return wrapped
# 定義函數(shù):完成包裹數(shù)據(jù)
def makeItalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
@makeBold
def test1():
return "hello world-1"
@makeItalic
def test2():
return "hello world-2"
@makeBold
@makeItalic
def test3():
return "hello world-3"
print(test1())
print(test2())
print(test3())
運(yùn)行結(jié)果:
<b>hello world-1</b>
<i>hello world-2</i>
<b><i>hello world-3</i></b>
4. 裝飾器示例
例1:無參數(shù)的函數(shù)
from time import ctime, sleep
def timefun(func):
def wrapped_func():
print("%s called at %s" % (func.__name__, ctime()))
func()
return wrapped_func
@timefun
def foo():
print("I am foo")
foo()
sleep(2)
foo()
上面代碼理解裝飾器執(zhí)行行為可理解成
foo = timefun(foo)
# foo先作為參數(shù)賦值給func后,foo接收指向timefun返回的wrapped_func
foo()
# 調(diào)用foo(),即等價(jià)調(diào)用wrapped_func()
# 內(nèi)部函數(shù)wrapped_func被引用旋炒,所以外部函數(shù)的func變量(自由變量)并沒有釋放
# func里保存的是原foo函數(shù)對象
例2:被裝飾的函數(shù)有參數(shù)
from time import ctime, sleep
def timefun(func):
def wrapped_func(a, b):
print("%s called at %s" % (func.__name__, ctime()))
print(a, b)
func(a, b)
return wrapped_func
@timefun
def foo(a, b):
print(a+b)
foo(3,5)
sleep(2)
foo(2,4)
例3:被裝飾的函數(shù)有不定長參數(shù)
from time import ctime, sleep
def timefun(func):
def wrapped_func(*args, **kwargs):
print("%s called at %s"%(func.__name__, ctime()))
func(*args, **kwargs)
return wrapped_func
@timefun
def foo(a, b, c):
print(a+b+c)
foo(3,5,7)
sleep(2)
foo(2,4,9)
例4:裝飾器中的return
from time import ctime, sleep
def timefun(func):
def wrapped_func():
print("%s called at %s" % (func.__name__, ctime()))
func()
return wrapped_func
@timefun
def foo():
print("I am foo")
@timefun
def getInfo():
return '----hahah---'
foo()
sleep(2)
foo()
print(getInfo())
執(zhí)行結(jié)果:
foo called at Fri Nov 4 21:55:35 2016
I am foo
foo called at Fri Nov 4 21:55:37 2016
I am foo
getInfo called at Fri Nov 4 21:55:37 2016
None
如果修改裝飾器為return func()
步悠,則運(yùn)行結(jié)果:
foo called at Fri Nov 4 21:55:57 2016
I am foo
foo called at Fri Nov 4 21:55:59 2016
I am foo
getInfo called at Fri Nov 4 21:55:59 2016
----hahah---
總結(jié):
- 一般情況下為了讓裝飾器更通用,可以有return
例5:裝飾器帶參數(shù),在原有裝飾器的基礎(chǔ)上瘫镇,設(shè)置外部變量
#decorator2.py
from time import ctime, sleep
def timefun_arg(pre="hello"):
def timefun(func):
def wrapped_func():
print("%s called at %s %s" % (func.__name__, ctime(), pre))
return func()
return wrapped_func
return timefun
# 下面的裝飾過程
# 1\. 調(diào)用timefun_arg("itcast")
# 2\. 將步驟1得到的返回值贤徒,即time_fun返回, 然后time_fun(foo)
# 3\. 將time_fun(foo)的結(jié)果返回汇四,即wrapped_func
# 4\. 讓foo = wrapped_fun接奈,即foo現(xiàn)在指向wrapped_func
@timefun_arg("itcast")
def foo():
print("I am foo")
@timefun_arg("python")
def too():
print("I am too")
foo()
sleep(2)
foo()
too()
sleep(2)
too()
可以理解為
foo()==timefun_arg("itcast")(foo)()
例6:類裝飾器(擴(kuò)展,非重點(diǎn))
裝飾器函數(shù)其實(shí)是這樣一個(gè)接口約束通孽,它必須接受一個(gè)callable對象作為參數(shù)序宦,然后返回一個(gè)callable對象。在Python中一般callable對象都是函數(shù)背苦,但也有例外互捌。只要某個(gè)對象重寫了 __call__()
方法潘明,那么這個(gè)對象就是callable的。
class Test():
def __call__(self):
print('call me!')
t = Test()
t() # call me
類裝飾器demo
class Test(object):
def __init__(self, func):
print("---初始化---")
print("func name is %s"%func.__name__)
self.__func = func
def __call__(self):
print("---裝飾器中的功能---")
self.__func()
#說明:
#1\. 當(dāng)用Test來裝作裝飾器對test函數(shù)進(jìn)行裝飾的時(shí)候秕噪,首先會創(chuàng)建Test的實(shí)例對象
# 并且會把test這個(gè)函數(shù)名當(dāng)做參數(shù)傳遞到__init__方法中
# 即在__init__方法中的屬性__func指向了test指向的函數(shù)
#
#2\. test指向了用Test創(chuàng)建出來的實(shí)例對象
#
#3\. 當(dāng)在使用test()進(jìn)行調(diào)用時(shí)钳降,就相當(dāng)于讓這個(gè)對象(),因此會調(diào)用這個(gè)對象的__call__方法
#
#4\. 為了能夠在__call__方法中調(diào)用原來test指向的函數(shù)體腌巾,所以在__init__方法中就需要一個(gè)實(shí)例屬性來保存這個(gè)函數(shù)體的引用
# 所以才有了self.__func = func這句代碼遂填,從而在調(diào)用__call__方法中能夠調(diào)用到test之前的函數(shù)體
@Test
def test():
print("----test---")
test()
showpy()#如果把這句話注釋,重新運(yùn)行程序澈蝙,依然會看到"--初始化--"
運(yùn)行結(jié)果如下:
---初始化---
func name is test
---裝飾器中的功能---
----test---