iT邦幫忙

2024 iThome 鐵人賽

DAY 21
0

第十九天:Python 中的裝飾器(Decorators)

內容概述:

今天我們將學習 Python 中一個非常強大的功能——裝飾器(Decorators)。裝飾器是 Python 函數式編程的重要特性,它們允許我們修改或增強現有的函數或方法,而不需要改變它們的原始程式碼。裝飾器在現實開發中常被用於日誌記錄、權限驗證、性能計算等場景。


一、什麼是裝飾器?

裝飾器是 Python 提供的一種語法糖,用於在不改變原始函數程式碼的情況下,為函數增添額外的功能。可以將裝飾器理解為一個函數,它接收另一個函數作為輸入,並返回一個增強版的函數。

基本語法範例:
def my_decorator(func):
    def wrapper():
        print("執行前")
        func()
        print("執行後")
    return wrapper

@my_decorator
def say_hello():
    print("Hello, world!")

say_hello()

在這個範例中,my_decorator 是一個裝飾器,它在 say_hello 函數執行前後增加了額外的行為。透過使用 @my_decorator,我們把裝飾器應用到了 say_hello 函數上。

輸出結果:

執行前
Hello, world!
執行後

二、為什麼要使用裝飾器?

裝飾器的核心優勢是重用性和簡化程式碼。假設我們在多個函數中需要執行相同的額外邏輯(例如,計算函數的執行時間、驗證使用者權限等),那麼我們可以通過裝飾器來統一管理這些邏輯,而不必為每個函數手動編寫重複的程式碼。


三、如何定義與使用裝飾器

1. 基本裝飾器範例

在這裡,我們將定義一個裝飾器,該裝飾器可以記錄被裝飾函數的執行時間。

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"函數 {func.__name__} 執行時間:{end_time - start_time:.4f} 秒")
        return result
    return wrapper

@timer
def example_function():
    time.sleep(2)
    print("函數正在執行...")

example_function()

這個範例使用了 time 模組來計算 example_function 的執行時間。透過 @timer,我們把這個計時功能應用到了 example_function 函數上。

輸出結果:

函數正在執行...
函數 example_function 執行時間:2.0001 秒

2. 裝飾器傳遞參數

我們也可以編寫可以傳遞參數的裝飾器,讓它的功能更加靈活。例如,寫一個裝飾器來控制日誌的打印級別。

def log(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == 'info':
                print(f"[INFO] 執行 {func.__name__} 函數")
            elif level == 'error':
                print(f"[ERROR] 執行 {func.__name__} 函數")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@log('info')
def process_data():
    print("處理數據中...")

process_data()

在這個範例中,我們定義了一個帶有參數的裝飾器 log,通過傳遞不同的日誌級別來控制輸出的內容。

輸出結果:

[INFO] 執行 process_data 函數
處理數據中...

四、實際應用場景

裝飾器在開發過程中非常常見,特別是當我們需要為多個函數添加相同的功能時。以下是幾個常見的應用場景:

1. 日誌記錄

裝飾器可以自動為函數添加日誌輸出,用於追蹤程式執行過程。

def log_execution(func):
    def wrapper(*args, **kwargs):
        print(f"執行 {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log_execution
def my_function():
    print("這是一個業務邏輯函數")

my_function()
2. 權限控制

在 Web 開發中,常常需要為某些敏感操作進行權限檢查。裝飾器可以方便地在函數調用前進行權限驗證。

def check_permission(role):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if role == 'admin':
                return func(*args, **kwargs)
            else:
                print("權限不足,無法執行此操作")
        return wrapper
    return decorator

@check_permission('user')
def delete_user():
    print("使用者已刪除")

delete_user()

這裡的裝飾器根據使用者的角色來決定是否執行刪除操作。


五、巢狀裝飾器

在 Python 中,我們可以同時使用多個裝飾器來為同一個函數增加多種功能。巢狀裝飾器的使用非常簡單,只需要在函數定義上連續使用多個 @ 符號即可。

@log_execution
@timer
def complex_calculation():
    time.sleep(1)
    print("執行複雜計算中...")

complex_calculation()

輸出結果:

執行 complex_calculation
執行複雜計算中...
函數 complex_calculation 執行時間:1.0002 秒

六、練習與實作

練習 1:
  • 寫一個裝飾器來驗證使用者輸入的數字是否為正數,如果不是則提示錯誤訊息。
練習 2:
  • 編寫一個裝飾器來記錄函數的調用次數,並在每次調用時打印出調用的總次數。

這兩個練習涉及 Python 的裝飾器,裝飾器是一種用來修改或增強函數行為的設計模式。通過裝飾器,你可以實現驗證、記錄等功能,而不需要更改原本的函數邏輯。以下是每個練習的詳細說明與範例代碼。


練習 1:寫一個裝飾器來驗證使用者輸入的數字是否為正數
步驟:
  1. 創建一個裝飾器,對使用者輸入的數字進行驗證。
  2. 如果數字為負數或 0,則提示錯誤訊息;如果為正數,則正常執行被裝飾的函數。
範例代碼:
# 定義驗證正數的裝飾器
def validate_positive_number(func):
    def wrapper(*args, **kwargs):
        for arg in args:  # 檢查所有傳入參數
            if not isinstance(arg, (int, float)) or arg <= 0:
                print(f"錯誤:{arg} 不是正數!")
                return  # 如果遇到錯誤值,停止執行函數
        return func(*args, **kwargs)
    return wrapper

# 應用裝飾器的函數
@validate_positive_number
def process_number(n):
    print(f"處理數字: {n}")

# 測試程式
process_number(10)   # 正數,應該正常執行
process_number(-5)   # 負數,應該提示錯誤
process_number(0)    # 0,應該提示錯誤
解釋:
  • *args, **kwargs:這樣可以處理任意數量的參數。
  • isinstance(arg, (int, float)):確保傳入的參數是整數或浮點數。
  • 如果輸入的數字不是正數,則裝飾器會顯示錯誤訊息並中止函數執行。

練習 2:編寫一個裝飾器來記錄函數的調用次數
步驟:
  1. 創建一個裝飾器來記錄函數的調用次數。
  2. 每次調用函數時,打印出調用的總次數。
範例代碼:
# 定義記錄調用次數的裝飾器
def count_calls(func):
    count = 0  # 記錄函數調用次數
    
    def wrapper(*args, **kwargs):
        nonlocal count  # 使用 nonlocal 來修改外層變數
        count += 1
        print(f"函數 {func.__name__} 已被調用 {count} 次")
        return func(*args, **kwargs)
    
    return wrapper

# 應用裝飾器的函數
@count_calls
def say_hello():
    print("Hello!")

# 測試程式
say_hello()  # 第一次調用
say_hello()  # 第二次調用
say_hello()  # 第三次調用
解釋:
  • count:裝飾器內部變數,用來計算函數的調用次數。
  • nonlocal count:讓內部函數可以修改外層的 count 變數。
  • 每次調用函數時,count 會自動遞增並打印總次數。

這兩個練習展示了如何使用裝飾器來處理函數參數驗證和函數調用次數記錄的功能。裝飾器提供了一種簡潔的方式來增強函數行為,無需修改原本的函數邏輯。


總結

裝飾器是一個非常有用的工具,能夠讓我們的程式碼更具模組化與可讀性。通過將公共邏輯抽象到裝飾器中,我們可以減少重複程式碼,提高程式的靈活性與可維護性。

明天的主題:Python 中的生成器(Generators)與迭代器(Iterators)


上一篇
跟著 ChatGPT成為程式大佬!Python 中的日期和時間
下一篇
跟著 ChatGPT成為程式大佬!Python 中的生成器&迭代器
系列文
如果讓chatgpt參加iThome鐵人賽,他竟然寫出...!?31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言