測開之函式進階· 第7篇《裝飾器裝飾類,通用裝飾器,有啥區別呢?》

清菡發表於2021-01-05

堅持原創輸出,點選藍字關注我吧

作者:清菡
部落格:oschina、雲+社群、知乎等各大平臺都有。

由於微信公眾號推送改為了資訊流的形式,防止走丟,請給加個星標 ⭐,你就可以第一時間接收到本公眾號的推送!

目錄

  • 一、什麼是裝飾器
    • 1.開放封閉原則(物件導向原則的核心)
    • 2.裝飾器的作用
  • 二、實現一個裝飾器
    • 1.不帶引數的裝飾器
    • 2.裝飾器的原理
    • 3.組裝方便,拆卸也方便
    • 4.帶引數的裝飾器
  • 三、通用裝飾器
  • 四、裝飾器裝飾類
    • 1.不帶引數的
    • 2.帶引數的
  • 五、裝飾器的應用場景
  • 六、補充
    • 1.*號的作用
    • 2.裝飾器裝飾類和裝飾函式的不同點

一、什麼是裝飾器

1.開放封閉原則(物件導向原則的核心)

對已經實現的功能(專案已經上線了),在這個基礎上增加新功能,也可以在它的基礎上進行擴充,這個就是開放。如果你要去再修改它內部的程式碼,這個時候是不允許的,對內部的修改是封閉的。

就是你實現的功能可以擴充,但是你不要去修改它內部的程式碼。

比如index()是介面,返回是“這個是網站的首頁”,只要呼叫這個介面就會返回一個“這個是網站的首頁。”

突然有個需求,在進入網站之前需要先登入校驗一下。這個時候需要擴充,如何擴充?就需要用裝飾器了。

def index():
    print("這個是網站的首頁")

這個已經實現的介面,不能去修改的。

2.裝飾器的作用

裝飾器可在不更改這個函式裡面任何程式碼的基礎上,給它新增新的功能。

二、實現一個裝飾器

1.不帶引數的裝飾器

裝飾器其實就是一種閉包的應用。要使用裝飾器,可以先定義個閉包函式。把登入校驗的功能寫在了閉包函式的內部。

把閉包函式當成裝飾器來用的話,外面接收的引數需要傳一個函式,你要裝飾哪個函式,你就傳哪個函式。


# 開放封閉原則
def login(func):
    def fun():
        # 簡單的校驗
        username = 'python01'
        password = 'qinghan'
        user = input("請輸入賬號:")
        pw = input("請輸入密碼:")
        # 判斷下賬號密碼對不對
        if username == user and pw == password:
            func()  # 登入得賬號密碼都正確的情況下,呼叫這個函式
        else:
            print("賬號或密碼錯誤")
    return fun


@login  # 艾特一下這個裝飾器
def index():
    print("這個是網站的首頁")


index()

2.裝飾器的原理

將被裝飾的函式當作一個引數傳到裝飾器中,並且讓被裝飾的函式名指向裝飾器內部的函式,在裝飾器的內部函式中用接收到的引數再呼叫被裝飾的函式。

@login是 Python 中的一個語法糖。它的作用是:index=login(index)。傳入index,然後被index接收。

如何做到通過func()呼叫原函式?

@login等於index=login(index)

自動將index當作引數傳入login這個函式裡面,去執行login(func)這個函式,檢測到這個fun()函式,將這段程式碼:

 def fun():
        # 簡單的校驗
        username = 'python01'
        password = 'qinghan'
        user = input("請輸入賬號:")
        pw = input("請輸入密碼:")
        # 判斷下賬號密碼對不對
        if username == user and pw == password:
            func()  # 登入得賬號密碼都正確的情況下,呼叫這個函式
        else:
            print("賬號或密碼錯誤")

直接跳過。

return fun直接將結果返回出來。結果返回出來又給index()接收,呼叫index()的時候實際上是呼叫fun()函式。

執行fun()函式裡面的程式碼。通過func()呼叫原函式,怎麼做到的?

fun()函式是放在index.__closure__這個屬性裡面。

然後在下面呼叫func()的時候,就是去index.__closure__這個屬性裡面找到對應儲存的那塊程式碼。

儲存的程式碼就是這個:

def index():
    print("這個是網站的首頁")

以上,就是裝飾器裝飾的流程。

3.組裝方便,拆卸也方便

我想改成不用登入也可以訪問,直接去掉@login這個裝飾器就可以了。


# 開放封閉原則
def login(func):
    def fun():
        # 簡單的校驗
        username = 'python01'
        password = 'qinghan'
        user = input("請輸入賬號:")
        pw = input("請輸入密碼:")
        # 判斷下賬號密碼對不對
        if username == user and pw == password:
            func()  # 登入得賬號密碼都正確的情況下,呼叫這個函式
        else:
            print("賬號或密碼錯誤")
    return fun


# @login  # 艾特一下這個裝飾器
def index():
    print("這個是網站的首頁")

# index.__closure__
index()

這樣操作,不會對原來有什麼影響。

4.帶引數的裝飾器

實現兩個數相加後,又有新的需求,需要可以相乘、相除。

def add(func):
    def fun(a, b):
        print("相乘", a * b)
        print("相除", a / b)
        func(a, b)

    return fun


@add
def add_num(a, b):
    # 列印兩個數相加
    print("相加:", a + b)


add_num(11, 22)

引數傳遞的過程

三、通用裝飾器

如果同一個裝飾器既要裝飾有引數的函式,又要裝飾無引數的函式。

那麼我們在傳參的時候就設定成不定長引數,這樣不管被裝飾的函式有沒有引數都能用。

# 通用裝飾器
def add(func):
    def fun(*args, **kwargs):
        print("裝飾器的功能程式碼:登入")
        func(*args,**kwargs)
    return fun


@add
def index():
    print("這個是網站的首頁")

@add
def good_list(num):
    print("這個是商品列表第{}頁".format(num))

index()
print("------------")
good_list(9)

四、裝飾器裝飾類

1.不帶引數的

#裝飾器裝飾類

def add(func):
    def fun(*args, **kwargs):
        print("裝飾器的功能程式碼:登入")
        return func(*args,**kwargs)
    return fun


@add  # MyClass=add(MyClass)
class MyClass:
    def __init__(self):
        pass

m = MyClass()
print("m的值:",m)

裝飾器裝飾類的原理

把類當作一個引數傳到裝飾器裡面。return fun返回的是funMyClass接收到的是fun

MyClass()呼叫的是fun

執行程式碼:

 def fun(*args, **kwargs):
        print("裝飾器的功能程式碼:登入")
        return func(*args,**kwargs)

這裡面的功能。

先執行裝飾器的功能,return func(*args,**kwargs)func()來自def add(func)

呼叫MyClass這個類,return func(*args,**kwargs)建立了個物件,MyClass()呼叫完了接收,m 就能接收這個物件了。

這個就是裝飾器裝飾類的一個原理。

2.帶引數的

#裝飾器裝飾類

def add(func):
    def fun(*args, **kwargs):
        print("裝飾器的功能程式碼:登入")
        return func(*args,**kwargs)
    return fun


@add  # MyClass=add(MyClass)
class MyClass:
    def __init__(self,name,age):
      self.name=name
      self.age=age

m = MyClass("qinghan","18")
print("m的值:",m)

這裡用的是不定行引數,所以不管你裝飾的類是有引數的還是沒引數的,都可以。

五、裝飾器的應用場景

1.登入校驗。(在裝飾器裡面判斷下你有沒有登入)

2.函式執行時間統計。

3.執行函式之前做準備工作。

4.執行函式後清理功能。

六、補充

1.*號的作用

*是進行拆包作用的。把每個元素拿出來,當作引數進行傳遞。

一個*是對元組形式的位置引數進行拆包,兩個**對關鍵字引數進行拆包。

2.裝飾器裝飾類和裝飾函式的不同點

類需要把物件返回出來。


公眾號清菡軟體測試首發,更多原創文章:清菡軟體測試 116+原創文章,歡迎關注、交流,禁止第三方擅自轉載。

相關文章