python基礎知識-11-函數裝飾器

 

 python其餘知識目錄html

 

一、裝飾器學習前熱身準備

1.1裝飾器簡介


1.2裝飾器熱身分析

1)
def func():
    pass
v1 = 10
v2 = func    #將函數名賦予一個變量,就和變量賦值是同理。而函數名賦予的是函數所在的內存地址
print(v1,v2)
------------結果:
10 <function func at 0x01E78D20>

2)
def base():
    print(1)
def bar():
    print(2)
bar = base  
bar() 
----------------結果:
1
#代碼解析:另bar從新賦值以後,它再也不指向以前的函數,而是指向base所指向的函數,
執行bar,就是執行base函數
3) def func(): def inner(): pass return inner v = func() print(v) # inner函數 --------結果: <function func.<locals>.inner at 0x00638D20> #代碼解析:func返回值是inner函數名,打印v,則顯示v指向inner所在的地址 4) def func(arg): def inner(): arg() return inner def f1(): print(123) v1 = func(f1) v1() ----------結果: 123 #代碼分析:將f1函數名做爲func的參數,執行func返回inner函數的地址。即v1是帶有參數爲
f1函數名的inner函數地址。執行v1函數,就是執行inner函數,而且參數爲f1。inner函數內部f1()
函數執行,再去找到f1函數,看它執行打印出123,f1的返回值是None。inner的返回值是None。
5) def func(arg): def inner(): arg() return inner def f1(): print(123) return 666 v1 = func(f1) result = v1() # 執行inner函數 / f1含函數 -> 123 print(result) # None ------------------結果: 123 None #代碼分析:(1)以f1函數名爲參數,執行func函數。(2)func函數執行,並將返回值即內部子函數inner
的地址賦值給v1。因爲func執行時實現了閉包,v1執行時的參數即爲f1(3)執行v1(),即執行inner函數,
執行inner函數後並將inner的返回值None賦值給result。執行inner函數期間,是將v1的參數f1函數執行
一下,打印123,並返回666.(4)result接收的是inner的返回值None,而不是f1執行的返回值。因此
打印None,而非666。
6) def func(arg): def inner(): return arg() return inner def f1(): print(123) return 666 v1 = func(f1) result = v1() # 執行inner函數 / f1含函數 -> 123 print(result) # 666 --------------結果: 123 666 #代碼分析:分析過程如5。區別在於result接收的值。這裏result依舊接收v1()返回的值。
v1指向inner函數,v1()的返回值是是inner的返回值。inner的返回值是arg()參數函數的返回值。
實現了閉包的,inner的參數是f1,因此result接收的就成了f1的返回值。f1的返回值是666,
因此result接收的值是666。因此打印666.而123打印的原理如5)
7) def func(): print(1) v1 = func func = 666 v1() print(func) ------------結果: 1 666 #代碼解析:func賦值給v1,v1和func共同指向func表明的函數內存地址
(至關於v1複製了一份func函數的地址信息,直接擁有找到函數的能力)。
func從新賦值,func變量指向666的內存地址。雖然func從新賦值了,可是不影響v1,執行v1,
仍是執行那個函數,而func倒是表明新的內容

二、裝飾器學習

1)
def func(arg):
    def inner():
        print('before')
        v = arg()
        print('after')
        return v
    return inner
def index():
    print('123')
    return '666'
(1)示例1:
v1 = index() # 執行index函數,打印123並返回666賦值給v1.
--------結果:
123
#代碼分析:執行index()函數,打印123,返回值index()‘666’賦值給v1
(2)示例2:
v2 = func(index) # v2是inner函數,arg=index函數
index = 666
v3 = v2()
------------結果:
before
123
after
#代碼分析:
執行函數func,參數爲index。
func返回值爲子函數inner,並賦值給v2。將index從新賦值666。執行v2(),由於實現了閉包,
因此v2執行時,就至關於執行帶了參數爲index的inner子函數。執行inner先打印before,
執行arg()就是執行了index函數.打印123,返回666給變量v。而後回到inner函數繼續往下執行。
打印after。返回666。inner的返回值666就是v2(),因此v3
=「666」。 v = arg() return v 實現了執行原函數並返回原函數的返回值。可是卻在函數執行以前和函數執行
以後都多作了別的操做。就是實現了給函數增添功能,這就是裝飾器。 (
3) v4 = func(index) index = v4 # index ==> inner index() ----------結果: before 123 after #代碼分析。與2相同。將index函數傳入func函數,func執行後返回inner函數地址給v4,index再接收
inner函數的地址,而後執行inner函數。因爲實現了閉包,這裏的args指向的是原來的index函數地址因此
執行後打印123.在函數執行前有作了其餘的打印操做。 (
4)裝飾器的一種表示方式 index = func(index) index() -------------結果: before 123 after #代碼解析:實則是將原函數index的地址做爲參數傳入到unc函數。而後在func函數的子函數中將這
個index原函數執行前執行後增添新的功能,利用閉包原理返回子函數名。並將子函數名從新賦值給
index函數。讓新的index函數在原有基礎上擁有了新的功能。 (
5)裝飾器的正確表示方式: def func(arg): def inner(): print("after") v = arg() return v return inner # 第一步:執行func函數並將下面的函數參數傳遞,至關於:func(index) # 第二步:將func的返回值從新賦值給下面的函數名。 index = func(index) @func def index(): print(123) return 666 print(index) index() -----------結果: <function func.<locals>.inner at 0x00587D20> after 123 #代碼分析:由上可知,index函數指向地址爲inner函數。也就是說。將函數地址傳到func函數加工裝飾以後
從新賦值給index函數。當這個函數再次執行時,就是找到新的函數的地址並執行。 執行過程爲:執行index()函數,當看到index上面有@func時,就說明使用了裝飾器,因而函數找到了func,
並將index以參數形式傳入。而後將func的返回值從新賦值給index,即index
= func(index)。
當index()執行時,就至關於inner()執行inner函數。inner函數內參數的執行仍是指向原index。
新index是指向inner函數的。inner函數的返回值定義爲原index的返回值。執行index原函數並執行原函數
先後新加的代碼,實現原函數功能不變的同時,增添了新的功能。而這些過程是inner函數執行的操做,
也就是新的index的執行的操做。因此,添加了裝飾器就實如今原函數不變的基礎上增添新的功能。

 

三、裝飾器的編寫和使用方法

1)# 裝飾器的編寫

def x(func):
def y(): #定義一個裝飾器,傳參爲要裝飾的函數,返回值爲子函數名。子函數名是要賦值給要裝飾的函數的,將要裝飾的函數從新賦予新值。。
# 前
ret = func() #裝飾器定義一個子函數。子函數裏執行傳進去的參數即要裝飾的函數。並接收要裝飾的函數的返回值,做爲子函數的返回值。由於
# 後 #要裝飾的函數名指向了裝飾器子函數的內存地址,即執行被裝飾後的新的函數就是執行這個子函數。因此子函數的返回值要和原函數即被
return ret #裝飾以前那個函數的返回值保持一致,子函數內部原函數被執行後的返回值做爲子函數的返回值,這樣就保持兩者一致了。這樣保證了
return y #原函數在被裝飾以前和裝飾以後的返回值不變,保證原函數的功能不變。在此基礎之上。再到原函數執行以前和以後添加新的功能,這樣就
# 裝飾器的應用 #實現了保證原函數不改變的狀況下,給原函數增添了新的功能(思考,原函數執行以前以後的操做是否是也能夠是個函數執行的過程呢,有時間驗證一下)python

@x
def index():
  return 10
@x
def manage():
  pass
# 執行函數,自動觸發裝飾器了
v = index()
print(v)
#裝飾器原理分析:
#定義一個裝飾器,傳參爲要裝飾的函數,返回值爲子函數名。子函數名是要賦值給要裝飾的函數的,將要裝飾的函數從新賦予新值。。 #裝飾器定義一個子函數。子函數裏執行傳進去的參數即要裝飾的函數。並接收要裝飾的函數的返回值,做爲子函數的返回值。由於
#要裝飾的函數名指向了裝飾器子函數的內存地址,即執行被裝飾後的新的函數就是執行這個子函數。因此子函數的返回值要和原函數即被
#裝飾以前那個函數的返回值保持一致,子函數內部原函數被執行後的返回值做爲子函數的返回值,這樣就保持兩者一致了。這樣保證了
#原函數在被裝飾以前和裝飾以後的返回值不變,保證原函數的功能不變。在此基礎之上。再到原函數執行以前和以後添加新的功能,這樣就
#實現了保證原函數不改變的狀況下,給原函數增添了新的功能(思考,原函數執行以前以後的操做是否是也能夠是個函數執行的過程呢,有時間驗證一下)緩存

[1]原函數:
def func():
    print("小馬過河")
func()
--------結果:
小馬過河
[2]原函數加上裝飾器後的代碼執行
def wrapper(func):
    def inner():
        print("我是",end="")
        return func()
    return inner
@wrapper
def func():
    print("小馬過河")
func()
-----------結果:
我是小馬過河

2)裝飾器編寫格式

def 外層函數(參數):
  def 內層函數(*args,**kwargs):
    return 參數(*args,**kwargs)
  return 內層函數閉包

#外層函數返回值爲內層函數名字。內存函數返回值爲要裝飾的函數的返回值(func())。內層函數要使用萬能傳參。內層參數返回值的參數也爲萬能傳參。記得要添加一個外層函數的參數接收變量
#內層函數和return原函數參數要統一,參數統一的目的是爲了給原來的index函數傳參app

實驗案例一:給已知爲單個傳參的函數添加裝飾器函數

[1]實驗案例   沒有添加裝飾器有一個參數的函數
def func(args):
    print(args)
func("小馬過河")
--------------結果:
小馬過河
[2]添加裝飾器可是裝飾器沒有接收參數的報錯狀況:
def wrapper(func):
    def inner():
        print("我是",end="")
        return func()
    return inner
@wrapper
def func(args):
    print(args)
func("小馬過河")
---------------結果:
func("小馬過河")
TypeError: inner() takes 0 positional arguments but 1 was given
[3]只給裝飾器中inner加上一個參數或者原函數被裝飾前執行的加一個參數都報錯
def wrapper(func):
    def inner(args):
        print("我是",end="")
        return func()
    return inner
@wrapper
def func(args):
    print(args)
func("小馬過河")
-----------------結果:
eturn func()
TypeError: func() missing 1 required positional argument: 'args'

def wrapper(func):
def inner(args):
print("我是"+args,end="")
return func()
return inner
@wrapper
def func():
print()
func("小馬過河")
------------結果:
我是小馬過河
#代碼分析:func("小馬過河")的內存地址是inner函數,inner函數有接收參數,函數運行正常。inner
內的func是原函數的地址,原函數沒有傳參,這裏也沒有傳參,沒有衝突,因此沒有問題。如此傳參都是
正常運行。
[4]給inner子函數和原函數執行都加上一個參數。
def wrapper(func):
    def inner(args):
        print("我是",end="")
        return func(args)
    return inner
@wrapper
def func(args):
    print(args)
func("小馬過河")
------------------結果:
我是小馬過河
[5]裝飾器中的兩個傳參的位置和func的傳參的位置接收變量的名字能夠不同。可是裝飾器中的兩個傳參的位置變量名字要同樣才能夠。
def wrapper(func):
    def inner(mcw):
        print("我是",end="")
        return func(mcw)
    return inner
@wrapper
def func(args):
    print(args)
func("小馬過河")
----------------------------結果:
我是小馬過河

#總結:由上可知。給有一個參數的函數添加裝飾器,在裝飾器的子函數以及子函數內執行的那個函數都要加上一個參數。本人以下分析:func是有參數的,須要傳參進入才能正常執行。func在子函數inner中執行func()也是須要參數才能夠正確執行。而裝飾後的新的func是指向inner函數的地址的,新的func執行須要參數位置,因此inner也要添加一個參數位置接收傳參。能夠這樣理解:即inner裏的參數爲新的func接收參數位置,inner裏面func執行的參數爲舊有的func接收參數的位置。新的func和舊有的func的傳遞的參數是一致的。此時有個疑問,就是新的func和舊有的func的函數地址是同樣的嗎,下面作個驗證:

def wrapper(func):
    def inner(mcw):
        print("我是",end="")
        print(func)
        return func(mcw)
    return inner
@wrapper
def func(args):
    print(args)
func("小馬過河")
print(func)
----------------結果:
我是<function func at 0x02137DB0>
小馬過河
<function wrapper.<locals>.inner at 0x02137D20>
#代碼分析總結:由上可知,print(func)時,裝飾器裏面原函數的地址是0x02137DB0,而外面打印是新的func地址0x02137D20。兩者明顯不一樣(那麼那個應該是舊有的地址呢,有時間研究一下)

實驗案例二:給未知多個傳參的函數添加裝飾器post

[1]原函數的執行狀況
def func(*args):
    mcw="".join(args)
    print(mcw)
func("小馬過河","的弟弟")
--------------結果:
小馬過河的弟弟
[2]加上裝飾器後傳參接收的個數不夠報錯
def wrapper(func):
    def inner(mcw):
        print("我是",end="")
        return func(mcw)
    return inner
@wrapper
def func(*args):
    mcw="".join(args)
    print(mcw)
func("小馬過河","的弟弟")
--------------------結果:
    func("小馬過河","的弟弟")
TypeError: inner() takes 1 positional argument but 2 were given
[3]傳參個數都設爲萬能傳參,裝飾器正常執行
def wrapper(func):
    def inner(*args):
        print("我是",end="")
        return func(*args)
    return inner
@wrapper
def func(*args):
    mcw="".join(args)
    print(mcw)
func("小馬過河","的弟弟")
----------------------結果:
我是小馬過河的弟弟

實驗三:**args的傳參
實驗四;*args和**args混合傳參

----------------------結果:

 

實驗三:**args的傳參
實驗四;*args和**args混合傳參學習

3)裝飾器應用格式

@外層函數
def index():
  pass
index()
#裝飾器的使用就是在原函數上面添加@外層函數(即裝飾器函數)。告訴這個函數是使用哪一個裝飾器。而後執行這個函數的時候這個函數就會找到裝飾器,並在這個函數執行時按裝飾器的來,本函數以及新增功能一併實現。ui

4)問題:爲何要加 *args, **kwargs

若是spa

 

四、帶參數的裝飾器

4.1帶參數的裝飾器案例解析

1】沒帶參數時是這樣的
def wrapper(func):
    def inner():
        return [func() for i in  range(0,5)]
    return inner
@wrapper
def func():
    print("mcw")
func()
----------------結果:
mcw
mcw
mcw
mcw
mcw
【2】給裝飾器中傳入參數是這樣的
def count(count):
    print("count")
    def wrapper(func):
        print("wrapper")
        def inner():
            return [func() for i in  range(0,count)]
        return inner
    return wrapper
@count(5)
def func():
    print("mcw")
------------------結果:
count
wrapper
#代碼分析:在@count(5)的時候發生了什麼呢。由上可知。執行了count函數,並將5做爲參數傳入參數。
打印「count」並返回wrapper函數名。這時@count(5)成了@wrapper。@wrapper執行wrapper函數
將func函數名做爲參數傳入函數,打印「wrapper」並返回inner函數名,而後從新賦值func指向inner函數。 【
3】給裝飾器傳入參數後執行被裝飾的函數。 def count(count): def wrapper(func): def inner(): return [func() for i in range(0,count)] return inner return wrapper @count(5) def func(): print("mcw") func() ---------------結果: mcw mcw mcw mcw mcw #代碼分析:由上可知,本裝飾器實現了將原函數執行5次的效果。而且次數是傳入的方便修改的參數。
給實現給裝飾器傳入參數的方法是在裝飾器函數外層上定義一個外層函數,並接收傳參。此處的傳參是須要傳入
裝飾器,想要在裝飾器中使用的參數。返回值是裝飾器的函數名。使用加了參數的裝飾器,由原來
的@wrapper改成了@count(
5)。在此過程當中,@count(5)外層實現了將參數5傳入裝飾器,而後轉
爲@wrapper的操做。再日後就是裝飾器的功能了,暫且不說。 #綜上: [
1]給裝飾器裏傳入參數的方法:在裝飾器函數外再套一個函數。這個外層函數接收的參數爲想要傳入裝飾器的
參數。外層函數的返回值是裝飾器的函數名。使用裝飾器由@裝飾器函數名改成@外層函數名的執行並加上參數
(即@外層函數(參數)) [
2]給裝飾器裏傳入參數的做用:裝飾器是用來批量裝飾函數的,若是不是每一個函數都要作相同的裝飾,
那麼就能夠在裝飾器中經過傳進來的參數進行判斷,給對應函數進行不一樣的裝飾。舉個栗子:多個函數,
每一個函數的執行此時能夠在@那裏定義一下,這樣每一個函數作了相同的裝飾可是又有不一樣的次數了;又好比,
裝飾器能夠用在緩存,djong的頁面緩存。在不一樣的頁面利用傳參判斷每一個頁面緩存時間是多少。 [
3]一個裝飾器根據不一樣函數實現增添不一樣的功能。。方法:添加傳參。而後作判斷。(兩撥函數作差別化裝飾,
可寫兩個裝飾器,也能夠寫一個帶傳參的裝飾器)
[4]帶傳參的裝飾器,能夠作對函數進行批量裝飾,又能作到差別化裝飾。

4.二、總結:

# 第一步:執行 ret = xxx(index)
# 第二步:將返回值賦值給 index = ret 
@xxx
def index():
    pass

# 第一步:執行 v1 = uuu(9)
# 第二步:ret = v1(index)
# 第三步:index = ret 
@uuu(9)
def index():
    pass
# ################## 普通裝飾器 #####################
def wrapper(func):
    def inner(*args,**kwargs):
        print('調用原函數以前')
        data = func(*args,**kwargs) # 執行原函數並獲取返回值
        print('調用員函數以後')
        return data
    return inner 

@wrapper
def index():
    pass

# ################## 帶參數裝飾器 #####################
def x(counter):
    def wrapper(func):
        def inner(*args,**kwargs):
            data = func(*args,**kwargs) # 執行原函數並獲取返回值
            return data
        return inner 
    return wrapper 

@x(9)
def index():
    pass

五、裝飾器的應用之一 批量計算函數執行時間

裝飾器:在不改變原函數內部代碼的基礎上,在函數執行以前和以後自動執行某個功能。

import time
def wrapper(func):
    def inner():
        start_time = time.time()
        v = func()
        end_time = time.time()
        print(end_time-start_time)
        return v
    return inner
@wrapper
def func1():
    time.sleep(2)
    print(123)
@wrapper
def func2():
    time.sleep(1)
    print(456)
func1()
func2()
-------------結果:
123
2.0001144409179688
456
1.0000572204589844 
#代碼分析:寫一個裝飾器,將每個使用裝飾器的函數,在原函數前面加一個時間,在原函數後面再加時間。
兩者之差就是函數的執行時間。因爲這裏函數執行太快,因而用sleep來加長原函數的執行時間。

六、裝飾器總結:

目的:在不改變原函數的基礎上,再函數執行先後自定義功能。應用場景:想要爲函數擴展功能時,能夠選擇用裝飾器。

相關文章
相關標籤/搜索