最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

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

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

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

核心思想包裝 (Wrapping)。

你可以把裝飾器想象成一個(gè)包裝禮物的過(guò)程:

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

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

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

2. 為什么需要裝飾器?(動(dòng)機(jī))

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

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

import time

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

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

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

問(wèn)題:

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

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

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

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

被賦值給一個(gè)變量

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

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

作為參數(shù)傳遞給另一個(gè)函數(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()  # 輸出: 你好,世界!

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

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

最簡(jiǎn)單的裝飾器(手動(dòng)方式)

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

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

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

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

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

輸出:

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

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

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

使用@語(yǔ)法糖

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

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

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

say_whee()

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

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

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

我們需要讓 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ù)開(kāi)始執(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"問(wèn)候了 {name}"

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

輸出:

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

6.functools.wraps的重要性

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

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

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

這對(duì)于調(diào)試和自省工具來(lái)說(shuō)非常不便。為了解決這個(gè)問(wèn)題,Python的 functools 模塊提供了一個(gè)專門的裝飾器:@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):
    """這是一個(gè)打招呼的函數(shù)"""
    print(f"你好, {name}!")

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

最佳實(shí)踐: 編寫任何裝飾器時(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),并返回一個(gè)真正的裝飾器
  2. 中間層函數(shù) (decorator_repeat):這就是我們之前寫的標(biāo)準(zhǔn)裝飾器,它接收一個(gè)函數(shù)作為參數(shù)。
  3. 最內(nèi)層函數(shù) (wrapper):執(zhí)行增強(qiáng)后的邏輯。
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í)行過(guò)程:

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

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

除了用函數(shù),我們也可以用類來(lái)創(chuàng)建裝飾器。這在需要維護(hù)狀態(tài)時(shí)特別有用。一個(gè)類要成為裝飾器,需要實(shí)現(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 類的實(shí)例 some_function 能夠記住自己被調(diào)用的次數(shù)。

9. 裝飾器的實(shí)際應(yīng)用場(chǎng)景(重點(diǎn))

日志記錄 (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)

性能計(jì)時(shí) (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)中非常常見(jiàn)。

# 這是一個(gè)簡(jiǎn)化的概念性例子
import functools

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

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

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

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

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

緩存 (Caching/Memoization)

對(duì)于計(jì)算成本高的函數(shù),可以將結(jié)果緩存起來(lái)。Python 3.9+ 自帶了 functools.cache。我們也可以自己實(shí)現(xiàn)一個(gè)簡(jiǎ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自帶的會(huì)更高效
# 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"第一次計(jì)算耗時(shí): {end - start:.4f}s")

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

輸入驗(yàn)證 (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") # 會(huì)拋出 TypeError
except TypeError as e:
    print(e)

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

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

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

這等價(jià)于:

complex_calculation = timer(log_function_call(complex_calculation))

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

  1. timerwrapper 開(kāi)始執(zhí)行,記錄開(kāi)始時(shí)間。
  2. timer 調(diào)用 log_function_callwrapper。
  3. log_function_callwrapper 開(kāi)始執(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é)束時(shí)間,并打印耗時(shí)。

11. 總結(jié)

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

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

相關(guān)文章

  • python學(xué)習(xí)筆記之列表(list)與元組(tuple)詳解

    python學(xué)習(xí)筆記之列表(list)與元組(tuple)詳解

    List(列表)是Python中使用最頻繁的數(shù)據(jù)類型,而元組是另一個(gè)數(shù)據(jù)類型,類似于List(列表)。這篇文章主要給大家介紹了python學(xué)習(xí)筆記之列表(list)與元組(tuple)的相關(guān)資料,文中不僅詳細(xì)介紹了兩者的使用,還分享了list和tuple的相互轉(zhuǎn)換的方法,需要的朋友可以參考下
    2017-11-11
  • 微信跳一跳輔助python代碼實(shí)現(xiàn)

    微信跳一跳輔助python代碼實(shí)現(xiàn)

    這篇文章主要為大家詳細(xì)介紹了微信跳一跳輔助的python代碼實(shí)現(xiàn)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • Python自動(dòng)掃描出微信不是好友名單的方法

    Python自動(dòng)掃描出微信不是好友名單的方法

    很多人想要清楚已經(jīng)被刪除的好友名單。面對(duì)龐大的好友數(shù)量想要清除談何容易,本文主要介紹了Python自動(dòng)掃描出微信不是好友名單的方法,感興趣的可以了解一下
    2021-05-05
  • python函數(shù)默認(rèn)參數(shù)使用避坑指南

    python函數(shù)默認(rèn)參數(shù)使用避坑指南

    這篇文章主要為大家介紹了python函數(shù)默認(rèn)參數(shù)使用的踩雷避坑詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • 從源碼解析Python的Flask框架中request對(duì)象的用法

    從源碼解析Python的Flask框架中request對(duì)象的用法

    Flask中的request對(duì)象發(fā)送請(qǐng)求使用起來(lái)十分方便,但也有一些需要注意的地方,這里我們來(lái)從源碼解析Python的Flask框架中request對(duì)象的用法,需要的朋友可以參考下.
    2016-06-06
  • python實(shí)現(xiàn)從本地?cái)z像頭和網(wǎng)絡(luò)攝像頭截取圖片功能

    python實(shí)現(xiàn)從本地?cái)z像頭和網(wǎng)絡(luò)攝像頭截取圖片功能

    這篇文章主要介紹了python實(shí)現(xiàn)從本地?cái)z像頭和網(wǎng)絡(luò)攝像頭截取圖片功能 ,文中給大家提到了python , opencv 打開(kāi)網(wǎng)絡(luò)攝像頭讀取圖像的實(shí)現(xiàn)代碼,需要的朋友可以參考下
    2019-07-07
  • python遍歷小寫英文字母的方法

    python遍歷小寫英文字母的方法

    今天小編就為大家分享一篇python遍歷小寫英文字母的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-01-01
  • 深入理解Python對(duì)Json的解析

    深入理解Python對(duì)Json的解析

    Json是一種常用的數(shù)據(jù)交換結(jié)構(gòu),由于輕量、易于閱讀和編寫等特點(diǎn),在網(wǎng)絡(luò)方面應(yīng)用很廣。下面這篇文章主要介紹了Python對(duì)Json解析的相關(guān)資料,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。
    2017-02-02
  • python+pytest自動(dòng)化測(cè)試函數(shù)測(cè)試類測(cè)試方法的封裝

    python+pytest自動(dòng)化測(cè)試函數(shù)測(cè)試類測(cè)試方法的封裝

    這篇文章主要介紹了python+pytest自動(dòng)化測(cè)試函數(shù)測(cè)試類測(cè)試方法的封裝,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-06-06
  • Python3.7實(shí)現(xiàn)中控考勤機(jī)自動(dòng)連接

    Python3.7實(shí)現(xiàn)中控考勤機(jī)自動(dòng)連接

    這篇文章主要為大家詳細(xì)介紹了Python3.7實(shí)現(xiàn)中控考勤機(jī)自動(dòng)連接,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-08-08

最新評(píng)論

监利县| 东港市| 芜湖县| 罗江县| 绿春县| 罗甸县| 蓝山县| 拉孜县| 华容县| 宝山区| 通州市| 新化县| 广元市| 兴城市| 遵义县| 海安县| 长武县| 通州市| 南木林县| 炎陵县| 武夷山市| SHOW| 攀枝花市| 佛山市| 中山市| 石嘴山市| 威信县| 榆社县| 万安县| 西乌| 闵行区| 施秉县| 靖江市| 县级市| 太保市| 太仓市| 吉水县| 耒阳市| 长沙县| 中山市| 万宁市|