搜尋
首頁後端開發Python教學必看的Python裝飾器的詳細介紹

講 Python 裝飾器前,我想先舉個例子,雖然有點污,但跟裝飾器這個主題很貼切。

每個人都有的內褲主要功能是用來遮羞,但是到了冬天它沒法為我們防風禦寒,咋辦?我們想到的一個辦法就是把內褲改造一下,讓它變得更厚更長,這樣一來,它不僅有遮羞功能,還能提供保暖,不過有個問題,這個內褲被我們改造成了長褲後,雖然還有遮羞功能,但本質上它不再是一條真正的內褲了。於是聰明的人發明長褲,在不影響內褲的前提下,直接把長褲套在了內褲外面,這樣內褲還是內褲,有了長褲後寶寶再也不冷了。裝飾器就像我們這裡說的長褲,在不影響內褲作用的前提下,給我們的身體提供了保暖的功效。

談裝飾器前,還要先明白一件事,Python 中的函數和Java、C++不太一樣,Python 中的函數可以像普通變數一樣當做參數傳遞給另外一個函數,例如:

def foo():
    print("foo")

def bar(func):
    func()

bar(foo)

正式回到我們的主題。裝飾器本質上是一個 Python 函數或類,它可以讓其他函數或類別在不需要做任何程式碼修改的前提下增加額外功能,裝飾器的回傳值也是一個函數/類別物件。它常用於有切面需求的場景,例如:插入日誌、效能測試、事務處理、快取、權限校驗等場景,裝飾器是解決這類問題的絕佳設計。有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同程式碼到裝飾器中並繼續重複使用。概括的講,裝飾器的作用就是為已經存在的物件添加額外的功能。

先來看一個簡單例子,雖然實際程式碼可能比這複雜很多:

def foo():
    print('i am foo')

現在有一個新的需求,希望可以記錄下函數的執行日誌,於是在程式碼中添加日誌程式碼:

def foo():
    print('i am foo')
    logging.info("foo is running")

如果函數bar()、bar2() 也有類似的需求,怎麼做?再寫一個 logging 在 bar 函數裡?這樣就造成大量雷同的程式碼,為了減少重複寫程式碼,我們可以這樣做,重新定義一個新的函數:專門處理日誌,日誌處理完之後再執行真正的業務代碼

def use_logging(func):
    logging.warn("%s is running" % func.__name__)
    func()

def foo():
    print('i am foo')

use_logging(foo)

這樣做邏輯上是沒問題的,功能是實現了,但是我們調用的時候不再是調用真正的業務邏輯foo 函數,而是換成了use_logging 函數,這就破壞了原有的代碼結構, 現在我們不得不每次都要把原來的那個foo 函數當作參數傳遞給use_logging 函數,那麼有沒有更好的方式的呢?當然有,答案就是裝飾器。

簡單裝飾器

def use_logging(func):

def wrapper():
        logging.warn("%s is running" % func.__name__)
return func()   # 把 foo 当做参数传递进来时,执行func()就相当于执行foo()
return wrapper

def foo():
    print('i am foo')

foo = use_logging(foo)  # 因为装饰器 use_logging(foo) 返回的时函数对象 wrapper,这条语句相当于  foo = wrapper
foo()                   # 执行foo()就相当于执行 wrapper()

use_logging 就是一個裝飾器,它一個普通的函數,它把執行真正業務邏輯的函數func 包裹在其中,看起來像foo 被use_logging 裝飾了一樣,use_logging 回傳的也是一個函數,這個函數的名字叫做wrapper。在這個例子中,函數進入和退出時 ,被稱為一個橫切面,這種編程方式被稱為面向切面的編程。

@ 語法糖

如果你接觸Python 有一段時間了的話,想必你對@ 符號一定不陌生了,沒錯@ 符號就是裝飾器的語法糖,它放在函數開始定義的地方,這樣就可以省略最後一步再次賦值的動作。

def use_logging(func):

def wrapper():
        logging.warn("%s is running" % func.__name__)
return func()
return wrapper

@use_logging
def foo():
    print("i am foo")

foo()

如上圖所示,有了 @ ,我們就可以省去foo = use_logging(foo)這一句了,直接呼叫 foo() 即可得到想要的結果。你們看到了沒有,foo() 函數不需要做任何修改,只要在定義的地方加上裝飾器,呼叫的時候還是和以前一樣,如果我們有其他的類似函數,我們可以繼續呼叫裝飾器來修飾函數,而不用重複修改函數或增加新的封裝。這樣,我們就提高了程式的可重複利用性,並增加了程式的可讀性。

裝飾器在Python 使用如此方便都要歸因於Python 的函數能像普通的物件一樣能作為參數傳遞給其他函數,可以被賦值給其他變量,可以作為返回值,可以被定義在另外一個函數內。

*args、**kwargs

可能有人問,如果我的業務邏輯函數 foo 需要參數怎麼辦?例如:

def foo(name):
    print("i am %s" % name)

我們可以在定義 wrapper 函數的時候指定參數:

def wrapper(name):
        logging.warn("%s is running" % func.__name__)
return func(name)
return wrapper

這樣 foo 函數定義的參數就可以定義在 wrapper 函數中。這時,又有人要問了,如果 foo 函數接收兩個參數呢?三個參數呢?更有甚者,我可能傳很多個。當裝飾器不知道foo 到底有多少個參數時,我們可以用*args 來代替:

def wrapper(*args):
        logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper

如此一來,甭管foo 定義了多少個參數,我都可以完整地傳遞到func 中去。這樣就不影響 foo 的業務邏輯了。這時還有讀者會問,如果 foo 函數還定義了一些關鍵字參數呢?例如:

def foo(name, age=None, height=None):
    print("I am %s, age %s, height %s" % (name, age, height))

這時,你就可以把 wrapper 函數指定關鍵字函數:

def wrapper(*args, **kwargs):
# args是一个数组,kwargs一个字典
        logging.warn("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper

带参数的装饰器

装饰器还有更大的灵活性,例如带参数的装饰器,在上面的装饰器调用中,该装饰器接收唯一的参数就是执行业务的函数 foo 。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。比如,我们可以在装饰器中指定日志的等级,因为不同业务函数可能需要的日志级别是不一样的。

def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
                logging.warn("%s is running" % func.__name__)
elif level == "info":
                logging.info("%s is running" % func.__name__)
return func(*args)
return wrapper

return decorator

@use_logging(level="warn")
def foo(name='foo'):
    print("i am %s" % name)

foo()

上面的 use_logging 是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。当我 们使用@use_logging(level=”warn”)调用的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中。

@use_logging(level=”warn”)等价于@decorator

类装饰器

没错,装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

class Foo(object):
def __init__(self, func):
        self._func = func

def __call__(self):
print ('class decorator runing')
        self._func()
print ('class decorator ending')

@Foo
def bar():
print ('bar')

bar()

functools.wraps

使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子:

# 装饰器
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__      # 输出 'with_logging'
print func.__doc__       # 输出 None
return func(*args, **kwargs)
return with_logging

# 函数
@logged
def f(x):
"""does some math"""
return x + x * x

logged(f)

不难发现,函数 f 被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器里面的 func 函数中,这使得装饰器里面的 func 函数也有和原函数 foo 一样的元信息了。

from functools import wraps
def logged(func):
    @wraps(func)
def with_logging(*args, **kwargs):
print func.__name__      # 输出 'f'
print func.__doc__       # 输出 'does some math'
return func(*args, **kwargs)
return with_logging

@logged
def f(x):
"""does some math"""
return x + x * x

装饰器顺序

一个函数还可以同时定义多个装饰器,比如:

@a
@b
@c
def f ():
    pass

它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于

f = a(b(c(f)



以上是必看的Python裝飾器的詳細介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
Python:遊戲,Guis等Python:遊戲,Guis等Apr 13, 2025 am 12:14 AM

Python在遊戲和GUI開發中表現出色。 1)遊戲開發使用Pygame,提供繪圖、音頻等功能,適合創建2D遊戲。 2)GUI開發可選擇Tkinter或PyQt,Tkinter簡單易用,PyQt功能豐富,適合專業開發。

Python vs.C:申請和用例Python vs.C:申請和用例Apr 12, 2025 am 12:01 AM

Python适合数据科学、Web开发和自动化任务,而C 适用于系统编程、游戏开发和嵌入式系统。Python以简洁和强大的生态系统著称,C 则以高性能和底层控制能力闻名。

2小時的Python計劃:一種現實的方法2小時的Python計劃:一種現實的方法Apr 11, 2025 am 12:04 AM

2小時內可以學會Python的基本編程概念和技能。 1.學習變量和數據類型,2.掌握控制流(條件語句和循環),3.理解函數的定義和使用,4.通過簡單示例和代碼片段快速上手Python編程。

Python:探索其主要應用程序Python:探索其主要應用程序Apr 10, 2025 am 09:41 AM

Python在web開發、數據科學、機器學習、自動化和腳本編寫等領域有廣泛應用。 1)在web開發中,Django和Flask框架簡化了開發過程。 2)數據科學和機器學習領域,NumPy、Pandas、Scikit-learn和TensorFlow庫提供了強大支持。 3)自動化和腳本編寫方面,Python適用於自動化測試和系統管理等任務。

您可以在2小時內學到多少python?您可以在2小時內學到多少python?Apr 09, 2025 pm 04:33 PM

兩小時內可以學到Python的基礎知識。 1.學習變量和數據類型,2.掌握控制結構如if語句和循環,3.了解函數的定義和使用。這些將幫助你開始編寫簡單的Python程序。

如何在10小時內通過項目和問題驅動的方式教計算機小白編程基礎?如何在10小時內通過項目和問題驅動的方式教計算機小白編程基礎?Apr 02, 2025 am 07:18 AM

如何在10小時內教計算機小白編程基礎?如果你只有10個小時來教計算機小白一些編程知識,你會選擇教些什麼�...

如何在使用 Fiddler Everywhere 進行中間人讀取時避免被瀏覽器檢測到?如何在使用 Fiddler Everywhere 進行中間人讀取時避免被瀏覽器檢測到?Apr 02, 2025 am 07:15 AM

使用FiddlerEverywhere進行中間人讀取時如何避免被檢測到當你使用FiddlerEverywhere...

Python 3.6加載Pickle文件報錯"__builtin__"模塊未找到怎麼辦?Python 3.6加載Pickle文件報錯"__builtin__"模塊未找到怎麼辦?Apr 02, 2025 am 07:12 AM

Python3.6環境下加載Pickle文件報錯:ModuleNotFoundError:Nomodulenamed...

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境