1. 程式人生 > >Python -- 面向物件進階之--裝飾器

Python -- 面向物件進階之--裝飾器

裝飾器

一、介紹:

裝飾器實際上就是為了給某程式增添功能,但該程式已經上線或已經被使用,那麼就不能大批量的修改原始碼,這樣是不科學的也是不現實的,因此就產生了裝飾器,使得其滿足:

  1. 不能修改被裝飾的函式的原始碼

  2. 不能修改被裝飾的函式的呼叫方式

  3. 滿足1、2的情況下給程式增添功能

那麼根據需求,同時滿足了這三點原則,這才是我們的目的。因為,下面我們從解決這三點原則入手來理解裝飾器。

二、先明白這段程式碼

def foo():
    print('foo')

foo #表示是函式
foo() #表示執行foo函式



def foo():
    print('foo')

foo = lambda x: x + 1
foo() # 執行下面的lambda表示式,而不再是原來的foo函式

三、需求來了

初創公司有N個業務部門,1個基礎平臺部門,基礎平臺負責提供底層的功能,如:資料庫操作、redis呼叫、監控API等功能。業務部門使用基礎功能時,只需呼叫基礎平臺提供的功能即可。如下:

############### 基礎平臺提供的功能如下 ###############
def f1():
    print('f1')

def f2():
    print('f2')

def f3():
    print('f3')

def f4():
    print('f4')


############### 業務部門A 呼叫基礎平臺提供的功能 ###############
f1()
f2()
f3()
f4()


############### 業務部門B 呼叫基礎平臺提供的功能 ###############
f1()
f2()
f3()
f4()

目前公司有條不紊的進行著,但是,以前基礎平臺的開發人員在寫程式碼時候沒有關注驗證相關的問題,即:基礎平臺的提供的功能可以被任何人使用。現在需要對基礎平臺的所有功能進行重構,為平臺提供的所有功能新增驗證機制,即:執行功能前,先進行驗證。

老大把工作交給 Low B,他是這麼做的:

跟每個業務部門交涉,每個業務部門自己寫程式碼,呼叫基礎平臺的功能之前先驗證。誒,這樣一來基礎平臺就不需要做任何修改了。太棒了,有充足的時間泡妹子...

當天Low B 被開除了…

老大把工作交給 Low BB,他是這麼做的:

############### 基礎平臺提供的功能如下 ###############
def f1():
    # 驗證1
    # 驗證2
    # 驗證3
    print('f1')

def f2():
    # 驗證1
    # 驗證2
    # 驗證3
    print('f2')

def f3():
    # 驗證1
    # 驗證2
    # 驗證3
    print('f3')

def f4():
    # 驗證1
    # 驗證2
    # 驗證3
    print('f4')

############### 業務部門不變 ###############
### 業務部門A 呼叫基礎平臺提供的功能###
f1()
f2()
f3()
f4()

### 業務部門B 呼叫基礎平臺提供的功能 ###
f1()
f2()
f3()
f4()

過了一週 Low BB 被開除了…

老大把工作交給 Low BBB,他是這麼做的:

只對基礎平臺的程式碼進行重構,其他業務部門無需做任何修改

############### 基礎平臺提供的功能如下 ###############
def check_login():
    # 驗證1
    # 驗證2
    # 驗證3
    pass

def f1():
    check_login()
    print('f1')

def f2():
    check_login()
    print('f2')

def f3():
    check_login()
    print('f3')

def f4():
    check_login()
    print('f4')

老大看了下Low BBB 的實現,嘴⻆漏出了一絲的欣慰的笑,語重心長的跟Low BBB聊了個天:

老大說:

寫程式碼要遵循 開放封閉 原則,雖然在這個原則是用的面向物件開發,但是也適用於函數語言程式設計,簡單來說,它規定已經實現的功能程式碼不允許被修改,但可以被擴充套件,即:
封閉:已實現的功能程式碼塊
開放:對擴充套件開發
如果將開放封閉原則應於在上述需求中,那麼就不允許在函式 f1 、f2、f3、f4的內部進行修改程式碼,老大就給了Low BBB一個實現先案:

def w1(func):
    def inner():
        # 驗證1
        # 驗證2
        # 驗證3
        func()
    return inner

@w1
def f1():
    print('f1')

@w1
def f2():
    print('f2')

@w1
def f3():
    print('f3')

@w1
def f4():
    print('f4')

對於上述程式碼,也是僅僅對基礎平臺的程式碼進行修改,就可以實現在其他人呼叫函式 f1 f2 f3 f4 之前都進行【驗證】操作,並且其他業務部門無需做任何操作。

Low BBB問了下老大,這段程式碼的內部執行原理是什麼呢?

python直譯器就會從上到下解釋程式碼,步驟如下:
1. def w1(func): ==>將w1函式載入到記憶體
2. @w1(不考慮後面的f2)
沒錯, 從表面上看直譯器僅僅會解釋這兩句程式碼,因為函式在沒有被呼叫之前其內部程式碼不會被執行。
從表面上看直譯器著實會執行這兩句,但是 @w1 這一句程式碼裡卻有大文章, @函式名是python的一種語法糖。

上例@w1內部會執行以下操作:

1、執行w1函式

執行w1函式 ,並將 @w1下面的函式作為w1函式的引數,即:@w1等價於 w1(f1) 所以,內部就會去執行inner函式:

2、w1的返回值

將執行完的w1函式返回值賦值給@w1下面的函式的函式名f1即將w1的返回值再重新賦值給 f1;

所以,以後業務部門想要執行f1函式時,就會執行新f1函式,在新f1函式內部先執行驗證,再執行原來的f1函式,然後將原來f1 函式的返回值返回給了業務呼叫者。

四、多個裝飾器

#定義函式:完成包裹資料
def makeBold(fn):
    def wrapped():
        print("----1---")
        return "<b>" + fn() + "</b>"
    return wrapped

#定義函式:完成包裹資料
def makeItalic(fn):
    def wrapped():
        print("----2---")
        return "<i>" + fn() + "</i>"
    return wrapped

@makeBold
@makeItalic
def test3():
    print("----3---")
    return "hello world-3"

ret = test3()
print(ret)

輸出結果:

----1---
----2---
----3---
<b><i>hello world-3</i></b>

當Python直譯器執行到@makeBold語句時,會判斷下面語句是不是一個函式,如果是函式就開始裝飾,如果不是則不進行裝飾。因為@makeBold語句後面是@makeItalic,則@makeItalic會去判斷下面的語句是不是一個函式,如果是函式就開始進行裝飾。所以當包含多個裝飾器時,會先用後面的裝飾器進行裝飾。

五、裝飾器什麼時候進行裝飾:

def w1(func):
    print("---正在裝飾1----")
    def inner():
        print("---正在驗證許可權1----")
        func()
    return inner

def w2(func):
    print("---正在裝飾2----")
    def inner():
        print("---正在驗證許可權2----")
        func()
    return inner

#只要python直譯器執行到了這個程式碼,那麼就會自動的進行裝飾,而不是等到呼叫的時候才裝飾的
@w1
@w2
def f1():
    print("---f1---")

#在呼叫f1之前,已經進行裝飾了
f1()

輸出結果:

---正在裝飾2----
---正在裝飾1----
---正在驗證許可權1----
---正在驗證許可權2----
---f1---

六. 裝飾器示例

例1:無引數的函式

def func(functionName):
    print("---func---1---")
    def func_in():
        print("---func_in---1---")
        functionName()
        print("---func_in---2---")

    print("---func---2---")
    return func_in

@func
def test():
    print("----test----")

#test = func(test)
test()

輸出結果:

---func---1---
---func---2---
---func_in---1---
----test----
---func_in---2---

上面的程式碼裝飾器執行行為可理解成
test = func(test) 
#test先作為引數賦值給functionName(即functionName指向了test),test接收指向func返回的func_in
test()
#呼叫test(),即等價呼叫func_in()
#內部函式func_in被引用,所以外部函式的functionName變數(自由變數)並沒有釋放
#functionName裡儲存的是原test函式物件

其實前面講的被裝飾的函式都是不帶引數的,下面我們講裝飾器對有引數的函式進行裝飾。

例2:被裝飾的函式有引數

def func(functionName):
    print("---func---1---")
    def func_in(a, b):#如果a,b 沒有定義,那麼會導致test(11,22)行的呼叫失敗
        print("---func_in---1---")
        functionName(a, b)#如果沒有把a,b當做實參進行傳遞,那麼會導致呼叫test()函式失敗
        print("---func_in---2---")

    print("---func---2---")
    return func_in

@func
def test(a, b):
    print("----test-a=%d,b=%d---"%(a,b))

test(11,22)

輸出結果:

---func---1---
---func---2---
---func_in---1---
----test-a=11,b=22---
---func_in---2---

例3:被裝飾的函式有不定長引數

def func(functionName):
    print("---func---1---")
    def func_in(*args, **kwargs):#採用不定長引數的方式滿足所有函式需要引數以及不需要引數的情況
        print("---func_in---1---")
        functionName(*args, **kwargs)#這個地方,需要寫*以及**,如果不寫的話,那麼args是元組,而kwargs是字典
        print("---func_in---2---")

    print("---func---2---")
    return func_in

@func
def test(a, b, c):
    print("----test-a=%d,b=%d,c=%d---"%(a,b,c))

@func
def test2(a, b, c, d):
    print("----test-a=%d,b=%d,c=%d,d=%d---"%(a,b,c,d))

test(11,22,33)
test2(44,55,66,77)

輸出結果:

---func---1---
---func---2---
---func---1---
---func---2---
---func_in---1---
----test-a=11,b=22,c=33---
---func_in---2---
---func_in---1---
----test-a=44,b=55,c=66,d=77---
---func_in---2---

例4:裝飾器中的return

def func(functionName):
    print("---func---1---")
    def func_in():
        print("---func_in---1---")
        ret = functionName() #儲存返回來的haha
        print("---func_in---2---")
        return ret #把haha返回到ret = test()行處的呼叫

    print("---func---2---")
    return func_in

@func
def test():
    print("----test----")
    return "haha"

ret = test()
print("test return value is %s"%ret)

輸出結果:

---func---1---
---func---2---
---func_in---1---
----test----
---func_in---2---
test return value is haha

例5:通用裝飾器

def func(functionName):
    def func_in(*args, **kwargs):
        print("-----記錄日誌-----")
        ret = functionName(*args, **kwargs)
        return ret

    return func_in

@func
def test():
    print("----test----")
    return "haha"

@func
def test2():
    print("----test2---")

@func
def test3(a):
    print("-----test3--a=%d--"%a)

ret = test()
print("test return value is %s"%ret)

a = test2()
print("test2 return value is %s"%a)

test3(11)

輸出結果:

-----記錄日誌-----
----test----
test return value is haha
-----記錄日誌-----
----test2---
test2 return value is None
-----記錄日誌-----
-----test3--a=11--

例6、帶有引數的裝飾器

def func_arg(arg):
    def func(functionName):
        def func_in():
            print("---記錄日誌-arg=%s--"%arg)
            if arg=="heihei":
                functionName()
                functionName()
            else:
                functionName()
        return func_in
    return func

#1. 先執行func_arg("heihei")函式,這個函式return 的結果是func這個函式的引用
#2. @func
#3. 使用@func對test進行裝飾
@func_arg("heihei")
def test():
    print("--test--")

#帶有引數的裝飾器,能夠起到在執行時,有不同的功能
@func_arg("haha")
def test2():
    print("--test2--")

test()
test2()

輸出結果:

---記錄日誌-arg=heihei--
--test--
--test--
---記錄日誌-arg=haha--
--test2--