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

python之裝飾器進階

開場白

昨天講了裝飾器的定義、入門、多重以及偽裝,今天的內容是裝飾器的進階。學python的朋友一定要先看閉包、再看裝飾器、最後再看裝飾器的進階。

帶引數裝飾器是關於裝飾器的最後一個難點,有疑惑可以留言探討,希望大家都能掌握好關於裝飾器的知識點。

帶引數裝飾器結構

大家都知道了裝飾器是用來拓展函式功能的,但是別忘了裝飾器本身也是函式,當然也可以通過給裝飾器增加引數來拓展功能。

我們繼續昨天的案例講解帶引數裝飾器:現在客戶又提需求了,客戶發現週一到週五收銀機的帳目很清楚,但是週六日賬目有點混亂。客戶希望收銀機把週六和週日的所有交易記錄儲存到日誌檔案中,週一到週五的交易記錄不需要儲存。

分析客戶需求,按照我們昨天學習的裝飾器的內容,可以很快想到解決方案,再寫一個裝飾器用來儲存交易記錄。但是這裡有一個難點,客戶要求週一至週五的不儲存,週六週日的儲存,怎麼辦呢?

在這裡就需要用到帶引數裝飾器了,具體程式碼如下:

import functools
from datetime import datetime
# 這行程式碼是獲取當前是星期幾,週一對應對應1,週六對應6,週日對應7
# day_week = datetime.now().isoweekday()  # 今天是週二,明天週三,不會觸發記錄日誌的條件,暫註釋,方便測試
day_week = 7  # 為了測試,假定今日是週日。實際使用時註釋這行,取消註釋上面一行。

def check_week(chk):
    def inner(func):
        @functools.wraps(func)
        def inner_chk(*args, **kwargs):
            if chk:
                with open('log.txt', mode='a', encoding='utf8') as f:
                    f.write(f'交易記錄:折扣值是{args[0]},商品單價{args[1]},商品數量{args[2]},交易時間是{datetime.now()}\n')
            return func(*args, **kwargs)
        return inner_chk
    return inner

def checkdisct(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)
    def inner(*args, **kwargs):
        pwd = input('請輸入密碼:')
        if pwd == "123456":
            print("密碼正確!")
            return func(*args, **kwargs)
        else:
            print('密碼錯誤!')
    return inner

# 判斷不是星期六和日則設定day_week為0,不觸發記錄log;若是週六、週日會觸發記錄log。
if day_week != 6 or day_week != 7:  
    day_week = 0

@checkpwd
@checkdisct
@check_week(day_week)
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)

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

測試結果完全滿足客戶的需求,實現了週一至五不記錄交易log,週六日記錄交易log。

帶引數裝飾器詳解

直接讀上面大段程式碼對於裝飾器運用不熟練的朋友可能會有些懵懂,下面詳細講解帶引數裝飾器。

帶引數裝飾器至少有3層結構,即最少包含3層def和3層return。

  1. 第一層:負責接收裝飾器自身的引數,再返回第二層函式。
  2. 第二層:負責接收被裝飾的函式,再返回第三層函式。
  3. 第三層:這一層做的事情很多,按功能劃分為3塊。
    1. 負責接收被裝飾函式的引數。
    2. 對裝飾器自身的引數進行解析處理,若其滿足某條件則做某動作,不滿足條件則不做動作(或做別的動作)。
    3. 返回被裝飾的函式及其引數。
def check_week(chk):  # 帶引數裝飾器,chk是判斷條件,chk不是0則記錄交易日誌,chk是0則不記錄
    def inner(func):  # func是被裝飾的函式名
        @functools.wraps(func)
        def inner_chk(*args, **kwargs):  # *args和**kwargs是被裝飾的函式引數
            if chk:  #判斷條件是否滿足,若不為0則將交易記錄和交易時間儲存到log.txt檔案
                with open('log.txt', mode='a', encoding='utf8') as f:  
                    f.write(f'交易記錄:折扣值是{args[0]},商品單價{args[1]},商品數量{args[2]},交易時間是{datetime.now()}\n')
            return func(*args, **kwargs)  # 返回被裝飾的函式及其引數
        return inner_chk  # 返回第三層函式inner_chk
    return inner  # 返回第二層函式inner

以上案例詳細講解了帶引數裝飾器的構造,要學習裝飾器必須多加練習才能真正掌握。

帶引數裝飾器的運用場景

關於帶引數裝飾器的運用場景有很多,比較常見的有是否記錄業務日誌,是否生成效能日誌,使用測試資料庫執行或使用生產資料庫執行等等,具體要因業務需求而定。

裝飾器的補充運用

關於裝飾器的運用,除了可以自己寫的裝飾器用,也有一些官方或第三方提供的裝飾器非常好用。例如之前有提及的@functools.wraps裝飾器。

這裡再補充一個numba的jit裝飾器,功能是通過即時編譯的方法提升python函式執行效率。在涉及大量迴圈運算的場合建議嘗試一下。以下是測試程式碼。

from numba import jit
import time

@jit
def fib(n):
    if n <= 2:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)

start = time.time()
print(fib(40))
end = time.time()
print(f"用時:{end - start}秒")

out:
102334155
用時:1.001574993133545秒

大家猜猜如果註釋掉@jit,執行時間時間需要多少秒?

執行效率相差21倍!!!

在此鼓勵一下學python的朋友,不要以為python執行效率低,其實深入學好python,學會各種優化方法可以大幅度提升執行效率。