天天看點

一文帶你了解Python裝飾器,進階用法實作緩存、日志、權限控制

作者:程式員梓羽同學
一文帶你了解Python裝飾器,進階用法實作緩存、日志、權限控制

一文帶你了解Python裝飾器

前言

當我們的Python代碼變得越來越複雜時,就可能會發現需要在函數中添加一些 額外的功能,例如 日志記錄、性能測試、輸入合法性檢查 等等。這時候,使用Python裝飾器就可以讓我們的代碼更加優雅和可維護。

裝飾器 是Python語言中的一種進階文法,它可以在不改變原有代碼的情況下,動态地為函數或者類添加功能。

本文小編将介紹裝飾器的實作原理、實作效果、适用場景,并且通過一些實際的例子來示範如何使用裝飾器來增強函數的功能和修改函數的行為。同時還有一些高階的裝飾器用法,例如類裝飾器、參數化裝飾器、多個裝飾器嵌套等等。

實作原理

裝飾器 的實作原理是利用了Python中的 閉包 和 函數對象 的特性。在Python中,函數和類都是一等公民,也就是說它們可以像普通變量一樣被傳遞、指派、作為參數和傳回值。

,它接受一個函數作為參數,并傳回一個新的函數。在傳回的新函數中,我們可以添加一些額外的功能,比如等等。然後再将這個新函數傳回,進而實作對原有函數的裝飾。

實作效果

使用裝飾器可以使代碼更加簡潔、優雅。在不改變原有代碼的情況下,可以動态地添加、修改、删除函數的功能。同時,裝飾器還可以提高代碼的複用性和可維護性,使代碼更加易讀易懂。

例如,我們可以使用裝飾器來給一個函數添加日志功能:

def log(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args {args} and kwargs {kwargs}")
        return func(*args, **kwargs)
    return wrapper

@log
def add(a, b):
    return a + b

print(add(1, 2))           

輸出:

Calling add with args (1, 2) and kwargs
3           

在上面的例子中,我們定義了一個log裝飾器,它接受一個函數作為參數,并傳回一個新的函數wrapper。在wrapper函數中,我們先列印出函數的名稱和參數,然後再調用原有的函數,并将結果傳回。最後,我們使用@log文法糖來裝飾add函數,進而實作了給add函數添加日志功能的效果。

适用場景

裝飾器可以用于很多場景,比如:

  1. 日志記錄:記錄函數的調用時間、參數和傳回值等資訊;
  2. 計時器:統計函數的執行時間;
  3. 緩存:緩存函數的計算結果,避免重複計算;
  4. 權限/身份驗證:驗證使用者是否有權限執行某個函數;
  5. 錯誤處理:捕獲函數執行過程中的異常,并進行處理等。
  6. 限制函數調用次數:給函數設定調用次數,單個程序或線程内或時間節點内不允許超出調用限制
  7. 重試:函數執行不符合預期時,進行重新調用執行

高階用法

除了基本的裝飾器文法外,還有一些高階的用法,可以讓裝飾器更加靈活和強大。

1、帶參數的裝飾器

有些時候,我們需要給裝飾器傳遞一些參數,比如日志的級别、緩存的大小等。為了實作帶參數的裝飾器,我們需要再定義一層函數,來接受裝飾器的參數,然後傳回一個真正的裝飾器函數。

def log(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"[{level}] Calling {func.__name__} with args {args} and kwargs {kwargs}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@log(level="INFO")
def add(a, b):
    return a + b

print(add(1, 2))           

輸出:

[INFO] Calling add with args (1, 2) and kwargs
3           

在上面的例子中,我們定義了一個帶參數的裝飾器log,它接受一個參數level,并傳回一個真正的裝飾器函數decorator。在decorator函數中,我們再定義一個wrapper函數,來實作日志記錄的功能。最後,我們使用@log(level="INFO")文法糖來裝飾add函數,并傳遞了一個參數level,進而實作了帶參數的裝飾器的效果。

2、類裝飾器

除了函數裝飾器外,Python中還支援類裝飾器。類裝飾器和函數裝飾器的實作原理是一樣的,隻是它接受的參數是一個類,而不是一個函數。

class Log:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print(f"Calling {self.func.__name__} with args {args} and kwargs {kwargs}")
        return self.func(*args, **kwargs)

@Log
def add(a, b):
    return a + b

print(add(1, 2))           

輸出:

Calling add with args (1, 2) and kwargs
3           

在上面的例子中,我們定義了一個類裝飾器Log,它接受一個函數作為參數,并在__call__方法中實作了日志記錄的功能。最後,我們使用@Log文法糖來裝飾add函數,并實作了類裝飾器的效果。

3、多裝飾器嵌套

多裝飾器嵌套也是一種進階用法,可以在一個函數上應用多個裝飾器,以實作更複雜的功能。裝飾器可以嵌套在一起,以便一個函數可以被多個裝飾器同時裝飾。

例如,我們可以定義一個裝飾器函數,用于記錄函數的執行時間:

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Time elapsed: {end_time - start_time:.2f} seconds")
        return result
    return wrapper           

然後,我們可以定義另一個裝飾器函數,用于緩存函數的結果:

def cache(func):
    cache_dict = {}

    def wrapper(*args, **kwargs):
        key = str(args) + str(kwargs)
        if key in cache_dict:
            print("Retrieving from cache...")
            return cache_dict[key]
        else:
            result = func(*args, **kwargs)
            cache_dict[key] = result
            return result

    return wrapper           

現在,我們可以定義一個函數,并将它裝飾上述兩個裝飾器:

@cache
@timer
def my_function(x, y):
    time.sleep(1)
    return x + y           

這意味着當我們調用 my_function 時,它會先被 cache 裝飾器裝飾,然後再被 timer 裝飾器裝飾。這樣,函數的結果會被緩存,并記錄函數的執行時間。

我們可以測試一下這個函數:

>>> my_function(2, 3)
Time elapsed: 1.00 seconds
5
>>> my_function(2, 3)
Retrieving from cache...
5           

可以看到,第一次調用函數時,它需要花費 1 秒鐘的時間來執行。但是,第二次調用函數時,它會從緩存中擷取結果,而不需要再次執行函數。

總結

Python裝飾器是一種強大且靈活的機制,允許我們靈活的增強函數的功能和修改函數的行為。學會使用裝飾器可以使你的代碼更加健壯,具有更好的可重用性。同時,高階裝飾器可以讓你更靈活的處理多種需求。在實際開發中,我們可以根據具體的需求,來選擇合适的裝飾器,并使用高階用法來實作更加靈活和強大的功能。