欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python裝飾器decorator實際應(yīng)用與高級使用詳解

 更新時間:2025年07月18日 09:20:39   作者:Tipriest_  
本文系統(tǒng)講解Python裝飾器,強調(diào)其作為函數(shù)包裝工具的核心作用,通過不修改原函數(shù)代碼實現(xiàn)功能擴展,適用于日志、計時、認(rèn)證等場景,利用函數(shù)一等公民特性提升代碼復(fù)用性與可維護(hù)性

在這篇文章中,我們來詳細(xì)、系統(tǒng)地講解一下Python的裝飾器(Decorator)。我會從基本概念講起,循序漸進(jìn),直到實際應(yīng)用和高級用法。

1. 什么是裝飾器?(核心思想)

裝飾器本質(zhì)上是一個Python函數(shù),它可以讓其他函數(shù)在不改變其源代碼的情況下增加額外的功能。

核心思想包裝 (Wrapping)。

你可以把裝飾器想象成一個包裝禮物的過程:

  • 你的函數(shù) = 禮物本身
  • 裝飾器 = 包裝紙、彩帶和蝴蝶結(jié)

禮物(函數(shù))的核心功能沒有變,但經(jīng)過包裝(裝飾器)后,它看起來更漂亮、更完整了(增加了新功能)。

在代碼層面,裝飾器是一個接收函數(shù)作為參數(shù),并返回一個新函數(shù)的函數(shù)。

2. 為什么需要裝飾器?(動機)

假設(shè)我們有很多函數(shù),現(xiàn)在想給每個函數(shù)都加上一個“計算運行時間”的功能。

不好的方法:修改每個函數(shù)

import time

def func_A():
    start_time = time.time()
    print("函數(shù)A開始執(zhí)行...")
    time.sleep(1)
    print("函數(shù)A執(zhí)行完畢。")
    end_time = time.time()
    print(f"函數(shù)A耗時: {end_time - start_time:.2f}秒")

def func_B():
    start_time = time.time()
    print("函數(shù)B開始執(zhí)行...")
    time.sleep(2)
    print("函數(shù)B執(zhí)行完畢。")
    end_time = time.time()
    print(f"函數(shù)B耗時: {end_time - start_time:.2f}秒")

# ... 還有 func_C, func_D ...

問題:

  • 代碼冗余:計時邏輯在每個函數(shù)里都重復(fù)了一遍。
  • 違反“開放封閉原則”:每次新增需求(比如再加個日志功能),都要去修改已有函數(shù)的代碼。

裝飾器就是為了解決這類問題而生的,它能將這些通用的、與核心業(yè)務(wù)無關(guān)的功能(如計時、日志、認(rèn)證)抽離出來。

3. Python基礎(chǔ):理解裝飾器的前提

要理解裝飾器,必須先理解Python中 “函數(shù)是一等公民” (Functions are First-Class Objects) 的概念。這意味著函數(shù)可以:

被賦值給一個變量

def say_hello():
    print("Hello!")

greet = say_hello  # 將函數(shù)say_hello賦值給變量greet
greet()  # 輸出: Hello!

作為參數(shù)傳遞給另一個函數(shù)

def do_something(func):
    print("準(zhǔn)備做某事...")
    func()  # 調(diào)用傳入的函數(shù)
    print("做完了。")

def work():
    print("正在努力工作...")

do_something(work)
# 輸出:
# 準(zhǔn)備做某事...
# 正在努力工作...
# 做完了。

在函數(shù)內(nèi)部定義,并作為返回值返回

def get_greeter():
    def greet():
        print("你好,世界!")
    return greet  # 返回內(nèi)部定義的greet函數(shù)

greeter_func = get_greeter()
greeter_func()  # 輸出: 你好,世界!

裝飾器正是利用了這幾點特性,尤其是第2點和第3點。

4. 一步步構(gòu)建你的第一個裝飾器

最簡單的裝飾器(手動方式)

我們來寫一個簡單的裝飾器,它在函數(shù)執(zhí)行前后打印信息。

# 1. 定義一個裝飾器函數(shù)
def my_decorator(func):
    # 2. 定義一個內(nèi)部包裝函數(shù),它將“包裝”原始函數(shù)
    def wrapper():
        print("在被裝飾的函數(shù)執(zhí)行之前...")
        func()  # 3. 調(diào)用原始函數(shù)
        print("在被裝飾的函數(shù)執(zhí)行之后...")
    
    # 4. 返回這個包裝好的函數(shù)
    return wrapper

# 定義一個需要被裝飾的函數(shù)
def say_whee():
    print("Whee!")

# 手動使用裝飾器
say_whee = my_decorator(say_whee)

# 現(xiàn)在調(diào)用say_whee,實際上是在調(diào)用wrapper函數(shù)
say_whee()

輸出:

在被裝飾的函數(shù)執(zhí)行之前...
Whee!
在被裝飾的函數(shù)執(zhí)行之后...

say_whee = my_decorator(say_whee) 這行代碼是理解裝飾器的關(guān)鍵。

它用 my_decorator 返回的 wrapper 函數(shù)覆蓋了原來的 say_whee 函數(shù)。

使用@語法糖

Python提供了一個更優(yōu)雅、更Pythonic的方式來使用裝飾器,那就是 @ 語法糖。

def my_decorator(func):
    def wrapper():
        print("在被裝飾的函數(shù)執(zhí)行之前...")
        func()
        print("在被裝飾的函數(shù)執(zhí)行之后...")
    return wrapper

@my_decorator  # 這行等價于 say_whee = my_decorator(say_whee)
def say_whee():
    print("Whee!")

say_whee()

輸出和上面完全一樣,但代碼是不是簡潔多了?

5. 處理帶參數(shù)的函數(shù)

如果我們的原始函數(shù)帶參數(shù)怎么辦?比如 greet(name)。上面的 wrapper 函數(shù)不接受任何參數(shù),會報錯。

我們需要讓 wrapper 函數(shù)能接受任意參數(shù),并把它們傳遞給原始函數(shù)。這里就要用到 *args**kwargs。

def decorator_with_args(func):
    # wrapper現(xiàn)在可以接受任意數(shù)量的位置參數(shù)和關(guān)鍵字參數(shù)
    def wrapper(*args, **kwargs):
        print("裝飾器:函數(shù)開始執(zhí)行")
        result = func(*args, **kwargs)  # 將參數(shù)傳遞給原始函數(shù),并保存返回值
        print("裝飾器:函數(shù)執(zhí)行完畢")
        return result  # 返回原始函數(shù)的執(zhí)行結(jié)果
    return wrapper

@decorator_with_args
def greet(name, message="你好"):
    print(f"{message}, {name}!")
    return f"問候了 {name}"

returned_value = greet("Alice", message="早上好")
print(f"函數(shù)返回值: {returned_value}")

輸出:

裝飾器:函數(shù)開始執(zhí)行
早上好, Alice!
裝飾器:函數(shù)執(zhí)行完畢
函數(shù)返回值: 問候了 Alice

6.functools.wraps的重要性

使用裝飾器有一個小問題:它會丟失原始函數(shù)的一些元信息(metadata),比如函數(shù)名 (__name__)、文檔字符串 (__doc__)等。

@decorator_with_args
def greet(name):
    """這是一個打招呼的函數(shù)"""
    print(f"你好, {name}!")

print(greet.__name__)  # 輸出: wrapper (而不是 greet)
print(greet.__doc__)   # 輸出: None (而不是 "這是一個打招呼的函數(shù)")

這對于調(diào)試和自省工具來說非常不便。為了解決這個問題,Python的 functools 模塊提供了一個專門的裝飾器:@wraps。

import functools

def decorator_for_wraps(func):
    @functools.wraps(func)  # 關(guān)鍵!將func的元信息拷貝到wrapper上
    def wrapper(*args, **kwargs):
        # ... 裝飾器邏輯 ...
        print("裝飾器邏輯...")
        return func(*args, **kwargs)
    return wrapper

@decorator_for_wraps
def greet(name):
    """這是一個打招呼的函數(shù)"""
    print(f"你好, {name}!")

print(greet.__name__)  # 輸出: greet
print(greet.__doc__)   # 輸出: 這是一個打招呼的函數(shù)

最佳實踐: 編寫任何裝飾器時,都應(yīng)該在你的 wrapper 函數(shù)上使用 @functools.wraps。

7. 帶參數(shù)的裝飾器(進(jìn)階)

如果我們想讓裝飾器本身也接收參數(shù)呢?比如 @repeat(num=3),讓函數(shù)重復(fù)執(zhí)行3次。

這就需要再加一層函數(shù)嵌套。

結(jié)構(gòu):

  1. 最外層函數(shù) (repeat):接收裝飾器的參數(shù)(如 num=3),并返回一個真正的裝飾器。
  2. 中間層函數(shù) (decorator_repeat):這就是我們之前寫的標(biāo)準(zhǔn)裝飾器,它接收一個函數(shù)作為參數(shù)。
  3. 最內(nèi)層函數(shù) (wrapper):執(zhí)行增強后的邏輯。
import functools

def repeat(num=2):  # 1. 最外層函數(shù),接收裝飾器參數(shù)
    def decorator_repeat(func):  # 2. 中間層,接收被裝飾的函數(shù)
        @functools.wraps(func)
        def wrapper(*args, **kwargs):  # 3. 最內(nèi)層,執(zhí)行邏輯
            print(f"函數(shù)將重復(fù)執(zhí)行 {num} 次。")
            for _ in range(num):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator_repeat

@repeat(num=4)
def say_hello(name):
    print(f"Hello, {name}!")

say_hello("World")

執(zhí)行過程:

  1. @repeat(num=4) 首先被調(diào)用,它返回 decorator_repeat 函數(shù)。
  2. Python接著執(zhí)行 @decorator_repeat,這等價于 say_hello = decorator_repeat(say_hello)
  3. 最終 say_hello 變量指向了 wrapper 函數(shù)。

8. 類裝飾器(進(jìn)階)

除了用函數(shù),我們也可以用類來創(chuàng)建裝飾器。這在需要維護(hù)狀態(tài)時特別有用。一個類要成為裝飾器,需要實現(xiàn) __init____call__ 方法。

class Counter:
    def __init__(self, func):
        functools.update_wrapper(self, func) # 類似 @wraps
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print(f"'{self.func.__name__}' 已被調(diào)用 {self.num_calls} 次。")
        return self.func(*args, **kwargs)

@Counter
def some_function():
    print("執(zhí)行 some_function")

some_function()
some_function()
some_function()

輸出:

'some_function' 已被調(diào)用 1 次。
執(zhí)行 some_function
'some_function' 已被調(diào)用 2 次。
執(zhí)行 some_function
'some_function' 已被調(diào)用 3 次。
執(zhí)行 some_function

Counter 類的實例 some_function 能夠記住自己被調(diào)用的次數(shù)。

9. 裝飾器的實際應(yīng)用場景(重點)

日志記錄 (Logging)

import functools
import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def log_function_call(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        logging.info(f"Calling function '{func.__name__}' with args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        logging.info(f"Function '{func.__name__}' returned {result}")
        return result
    return wrapper

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

add(5, 3)

性能計時 (Timing)

import time
import functools

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        run_time = end_time - start_time
        print(f"Function '{func.__name__}' took {run_time:.4f} seconds to complete.")
        return result
    return wrapper

@timer
def process_data():
    time.sleep(1)
    print("數(shù)據(jù)處理完成!")

process_data()

用戶認(rèn)證 (Authentication)

在Web框架(如Flask, Django)中非常常見。

# 這是一個簡化的概念性例子
import functools

def login_required(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 偽代碼:假設(shè)有一個函數(shù) check_user_logged_in()
        if check_user_logged_in():
            return func(*args, **kwargs)
        else:
            raise PermissionError("用戶未登錄,禁止訪問!")
    return wrapper

# 模擬一個全局登錄狀態(tài)
_user_is_logged_in = False
def check_user_logged_in(): return _user_is_logged_in

@login_required
def view_profile():
    print("這是用戶的個人資料頁面。")

# 嘗試調(diào)用
try:
    view_profile()
except PermissionError as e:
    print(e)  # 輸出: 用戶未登錄,禁止訪問!

# 模擬用戶登錄
_user_is_logged_in = True
print("\n用戶登錄后:")
view_profile() # 輸出: 這是用戶的個人資料頁面。

緩存 (Caching/Memoization)

對于計算成本高的函數(shù),可以將結(jié)果緩存起來。Python 3.9+ 自帶了 functools.cache。我們也可以自己實現(xiàn)一個簡單的版本。

import functools
import time

def memoize(func):
    cache = {}
    @functools.wraps(func)
    def wrapper(*args):
        if args in cache:
            return cache[args]
        result = func(*args)
        cache[args] = result
        return result
    return wrapper

# 使用Python自帶的會更高效
# from functools import lru_cache, cache

@memoize # 或者 @functools.cache
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

start = time.time()
print(fibonacci(35))
end = time.time()
print(f"第一次計算耗時: {end - start:.4f}s")

start = time.time()
print(fibonacci(35))
end = time.time()
print(f"第二次(從緩存)計算耗時: {end - start:.4f}s")

輸入驗證 (Validation)

在函數(shù)執(zhí)行前檢查其參數(shù)是否符合要求。

import functools

def validate_types(*type_args):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for i, (arg, expected_type) in enumerate(zip(args, type_args)):
                if not isinstance(arg, expected_type):
                    raise TypeError(f"Argument {i+1} must be of type {expected_type.__name__}, not {type(arg).__name__}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@validate_types(int, int)
def multiply(a, b):
    return a * b

print(multiply(5, 10))  # 正常工作
try:
    multiply(5, "10") # 會拋出 TypeError
except TypeError as e:
    print(e)

10. 裝飾器棧(多個裝飾器)

一個函數(shù)可以被多個裝飾器修飾。執(zhí)行順序是從下到上(最靠近函數(shù)的先被應(yīng)用),然后執(zhí)行時是從上到下。

@timer
@log_function_call
def complex_calculation(x, y):
    time.sleep(0.5)
    return x * y + 2

這等價于:

complex_calculation = timer(log_function_call(complex_calculation))

執(zhí)行 complex_calculation(3, 4) 時:

  1. timerwrapper 開始執(zhí)行,記錄開始時間。
  2. timer 調(diào)用 log_function_callwrapper
  3. log_function_callwrapper 開始執(zhí)行,打印日志 “Calling function…”。
  4. log_function_call 調(diào)用原始的 complex_calculation 函數(shù)。
  5. complex_calculation 執(zhí)行,返回結(jié)果 14。
  6. log_function_callwrapper 拿到結(jié)果,打印日志 “…returned 14”。
  7. timerwrapper 拿到結(jié)果,記錄結(jié)束時間,并打印耗時。

11. 總結(jié)

  • 核心:裝飾器是一個接收函數(shù)并返回新函數(shù)的函數(shù),用于在不修改原函數(shù)代碼的情況下增加功能。
  • 基礎(chǔ):依賴于Python中函數(shù)是“一等公民”的特性。
  • 語法糖@decoratormy_func = decorator(my_func) 的簡寫。
  • 通用性:使用 *args, **kwargs 來處理任意參數(shù)的函數(shù)。
  • 最佳實踐:始終使用 @functools.wraps 來保留原函數(shù)的元信息。
  • 靈活性:裝飾器可以帶參數(shù),也可以用類來實現(xiàn)。
  • 強大應(yīng)用:日志、計時、認(rèn)證、緩存、驗證等都是裝飾器的經(jīng)典用例,極大地提高了代碼的復(fù)用性可維護(hù)性

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Python實現(xiàn)自動合并Word并添加分頁符

    Python實現(xiàn)自動合并Word并添加分頁符

    這篇文章主要為大家詳細(xì)介紹了如何基于Python實現(xiàn)對多個Word文檔加以自動合并,并在每次合并時按要求增添一個分頁符的功能,感興趣的可以了解一下
    2023-02-02
  • Python網(wǎng)絡(luò)編程中urllib2模塊的用法總結(jié)

    Python網(wǎng)絡(luò)編程中urllib2模塊的用法總結(jié)

    使用urllib2模塊進(jìn)行基于url的HTTP請求等操作大家也許都比較熟悉,這里我們再深入來了解一下urllib2針對HTTP的異常處理相關(guān)功能,一起來看一下Python網(wǎng)絡(luò)編程中urllib2模塊的用法總結(jié):
    2016-07-07
  • 關(guān)于python中模塊和重載的問題

    關(guān)于python中模塊和重載的問題

    這篇文章主要介紹了python模塊和重載的問題,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-11-11
  • Python中read,readline和readlines的區(qū)別案例詳解

    Python中read,readline和readlines的區(qū)別案例詳解

    這篇文章主要介紹了Python中read,readline和readlines的區(qū)別案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-09-09
  • python實現(xiàn)中文輸出的兩種方法

    python實現(xiàn)中文輸出的兩種方法

    這篇文章主要介紹了python實現(xiàn)中文輸出的兩種方法,實例分析了Python操作中文輸出的技巧,需要的朋友可以參考下
    2015-05-05
  • Python常見字符串操作函數(shù)小結(jié)【split()、join()、strip()】

    Python常見字符串操作函數(shù)小結(jié)【split()、join()、strip()】

    這篇文章主要介紹了Python常見字符串操作函數(shù),結(jié)合實例形式總結(jié)分析了split()、join()及strip()的常見使用技巧與注意事項,需要的朋友可以參考下
    2018-02-02
  • 使用python?matplotlib畫折線圖實例代碼

    使用python?matplotlib畫折線圖實例代碼

    Matplotlib是一個Python工具箱,用于科學(xué)計算的數(shù)據(jù)可視化,下面這篇文章主要給大家介紹了關(guān)于如何使用python?matplotlib畫折線圖的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-04-04
  • Python正則表達(dá)式分組概念與用法詳解

    Python正則表達(dá)式分組概念與用法詳解

    這篇文章主要介紹了Python正則表達(dá)式分組概念與用法,結(jié)合具體實例形式較為詳細(xì)的分析了Python正則表達(dá)式中分組、引用、斷言等概念與相關(guān)使用技巧,需要的朋友可以參考下
    2017-06-06
  • 使用Dataframe.info()顯示空值與類型信息

    使用Dataframe.info()顯示空值與類型信息

    這篇文章主要介紹了使用Dataframe.info()顯示空值與類型信息,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-02-02
  • pyhon如何把程序打包為whl

    pyhon如何把程序打包為whl

    這篇文章主要介紹了pyhon如何把程序打包為whl問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03

最新評論