1. 程式人生 > 實用技巧 >python之裝飾器詳解

python之裝飾器詳解

裝飾器的定義

裝飾器是一個函式,它可以不改變另外一個函式的程式碼給其新增新功能。這是參與多人專案必須要學會的技能,學python可不能錯過裝飾器。

裝飾器的入門

要掌握裝飾器先得理解閉包,如果還沒掌握閉包的朋友可以先看看我昨天寫的關於閉包的內容,掌握了閉包以後再學裝飾器就很容易了。今天繼續昨天閉包的案例來講裝飾器。

首先我們有一個計算商品出售時應付款和實付款的函式,程式碼如下:

def count(x, prince, number):  # x是折扣比例,prince是單價,number是數量
    result = prince * number  # result是應付款,等於prince乘以number
    pay = result * x  # pay是實付款,等於應付款乘以x折扣比例
    print(f'總價是{result}元,實付{pay}元')

現在客戶提了新的需求,要求執行count前先校驗密碼,密碼不對的不能執行,密碼對的才能執行。

一般來說要滿足新的需求肯定得改動相應的函式才能辦到,但是在大型專案裡改動不是自己寫的的函式很容易引起問題。

在python中有一種不需要改動原來函式的程式碼就能對其增加功能的好辦法。辦法如下:

def checkpwd(func):  # 實現密碼校驗功能的裝飾器
    def inner(*args, **kwargs):
        pwd = input('請輸入密碼:')
        if pwd == "123456":
            print("密碼正確!")
            return func(*args, **kwargs)  # 執行函式前校驗密碼,密碼對才能執行
        else:
            print('密碼錯誤')
    return inner

@checkpwd  # 裝飾器。功能等價於count=checkpwd(count)
def count(x, prince, number):
    result = prince * number
    pay = result * x
    print(f'總價是{result}元,實付{pay}元')

count(0.8, 2.88, 100)


out:
請輸入密碼:123456
密碼正確!
總價是288.0元,實付230.4元

多重灌飾器

現在客戶又提出了新的需求,執行count前先要校驗折扣值,值的範圍必須在0.5和1之間。

那麼我們需要再寫一個校驗折扣值範圍的裝飾器,程式碼如下:

def checkdisct(func):
    def inner(*args, **kwargs):
        disct = args[0]
        if disct >= 0.5 and disct <= 1:
            print('折扣值合理!')
            return func(*args, **kwargs)
        else:
            print('折扣值不合理!')

    return inner


def checkpwd(func):
    def inner(*args, **kwargs):
        pwd = input('請輸入密碼:')
        if pwd == "123456":
            print("密碼正確!")
            return func(*args, **kwargs)
        else:
            print('密碼錯誤!')

    return inner

@checkpwd
@checkdisct
def count(x, prince, number):
    result = prince * number
    pay = result * x
    print(f'總價是{result}元,實付{pay}元')


count(0.8, 2.88, 100)
count(0.3, 2.88, 100)

out:
請輸入密碼:123456
密碼正確!
折扣值合理!
總價是288.0元,實付230.4元
請輸入密碼:1234
密碼錯誤!

注意,多重灌飾器需要注意載入順序和執行順序。

  1. 裝飾器的載入順序是由內而外,以上案例中載入順序是先載入checkdisct函式,後加載checkpwd函式。好比穿衣服,先穿內衣,後穿外衣。
  2. 裝飾器的執行順序是由外而內,以上案例中執行順序是先執行完checkpwd函式,後執行完checkdisct函式。好比脫衣服,先脫外衣,再脫內衣。

裝飾器的偽裝

通過以上案例我們學習了用裝飾器的功能來實現不改動原來函式的基礎上給其新增功能,但是還存在一個重要的細節沒有做好。就是被裝飾的函式說明文件會被遮蔽。說明文件是非常關鍵的資訊,我們可以用如下的方法實現既能用好裝飾器又能保證原函式的說明文件資訊不被遮蔽。

這段是未加裝飾器的函式,列印說明文件內容正常。

def count(x, prince, number):
    '''功能:計算商品應付款和實付款的函式。
引數:x是float型,指定折扣額度;prince是float型,指定商品的單價;number是int型,指定商品的數量。'''
    result = prince * number
    pay = result * x
    print(f'總價是{result}元,實付{pay}元')

print(count.__doc__)
out:
功能:計算商品應付款和實付款的函式。
引數:x是float型,指定折扣額度;prince是float型,指定商品的單價;number是int型,指定商品的數量。

如果需要加了裝飾器還能正常列印函式的說明文件需要這樣做:

import functools  # 匯入函式工具模組


def checkdisct(func):
    @functools.wraps(func)  # 使用functools模組的wraps函式,儲存func的說明文件
    def inner(*args, **kwargs):
        disct = args[0]
        if disct >= 0.5 and disct <= 1:
            print('折扣值合理!')
            return func(*args, **kwargs)
        else:
            print('折扣值不合理!')

    return inner


def checkpwd(func):
    @functools.wraps(func)  # 使用functools模組的wraps函式,儲存func的說明文件
    def inner(*args, **kwargs):
        pwd = input('請輸入密碼:')
        if pwd == "123456":
            print("密碼正確!")
            return func(*args, **kwargs)
        else:
            print('密碼錯誤!')

    return inner


@checkpwd
@checkdisct
def count(x, prince, number):
    '''功能:計算商品應付款和實付款的函式。
引數:x是float型,指定折扣額度;prince是float型,指定商品的單價;number是int型,指定商品的數量。'''
    result = prince * number
    pay = result * x
    print(f'總價是{result}元,實付{pay}元')


# count(0.8, 2.88, 100)
# count(0.3, 2.88, 100)
print(count.__doc__)

out:
功能:計算商品應付款和實付款的函式。
引數:x是float型,指定折扣額度;prince是float型,指定商品的單價;number是int型,指定商品的數量。

最後

裝飾器的內容還有一節,關於裝飾器本身引數,留待明天再詳細講。

關於裝飾器內容不少,但是並不難,要學好裝飾器需要多多練習才能真正掌握。

希望學python的朋友都能掌握好裝飾器。