深入了解和應(yīng)用Python 裝飾器 @decorator
Python的裝飾器(decorator)是一個很棒的機制,也是熟練運用Python的必殺技之一。裝飾器,顧名思義,就是用來裝飾的,它裝飾的是一個函數(shù),保持被裝飾函數(shù)的原有功能,再裝飾上(添油加醋)一些其它功能,并返回帶有新增功能的函數(shù)對象,所以裝飾器本質(zhì)上是一個返回函數(shù)對象的函數(shù)(確切的說,裝飾器應(yīng)該是可調(diào)用對象,除了函數(shù),類也可以作為裝飾器)。
在編程過程中,我們經(jīng)常遇到這樣的場景:登錄校驗,權(quán)限校驗,日志記錄等,這些功能代碼在各個環(huán)節(jié)都可能需要,但又十分雷同,通過裝飾器來抽象、剝離這部分代碼可以很好解決這類場景。
裝飾器是什么?
要理解Python的裝飾器,首先我們先理解一下Python的函數(shù)對象。我們知道,在Python里一切都是對象,函數(shù)也不例外,函數(shù)是第一類對象(first-class objects),它可以賦值給變量,也可以作為list的元素,還可以作為參數(shù)傳遞給其它函數(shù)。
函數(shù)可以被變量引用
定義一個簡單的函數(shù):
def say_hi(): print('Hi!') say_hi() # Output: Hi!
個變量say_hi2來引用say_hi函數(shù):
say_hi2 = say_hi print(say_hi2) # Output: <function say_hi at 0x7fed671c4378> say_hi2() # Output: Hi!
上面的語句中say_hi2 和 say_hi 指向了同樣的函數(shù)定義,二者的執(zhí)行結(jié)果也相同。
函數(shù)可以作為參數(shù)傳遞給其它函數(shù)
def say_more(say_hi_func): print('More') say_hi_func() say_more(say_hi) # Output: # More # Hi
在上面的例子中,我們把say_hi函數(shù)當做參數(shù)傳遞給say_more函數(shù),say_hi 被變量 say_hi_func 引用。
函數(shù)可以定義在其它函數(shù)內(nèi)部
def say_hi(): print('Hi!') def say_name(): print('Tom') say_name() say_hi() # Output: # Hi! # Tom say_name() # 報錯
上述代碼中,我們在say_hi()函數(shù)內(nèi)部定義了另外一個函數(shù)say_name()。say_name()只在say_hi函數(shù)內(nèi)部可見(即,它的作用域在say_hi函數(shù)內(nèi)部),在say_hi外包調(diào)用時就會出錯。
函數(shù)可以返回其它函數(shù)的引用
def say_hi(): print('Hi!') def say_name(): print('Tom') return say_name say_name_func = say_hi() # 打印Hi!,并返回say_name函數(shù)對象 # 并賦值給say_name_func say_name_func() # 打印 Tom
上面的例子,say_hi函數(shù)返回了其內(nèi)部定義的函數(shù)say_name的引用。這樣在say_hi函數(shù)外部也可以使用say_name函數(shù)了。
前面我們理解了函數(shù),這有助于我們接下來弄明白裝飾器。
裝飾器(Decorator)
裝飾器是可調(diào)用對象(callable objects),它用來修改函數(shù)或類。
可調(diào)用對象就是可以接受某些參數(shù)并返回某些對象的對象。Python里的函數(shù)和類都是可調(diào)用對象。
函數(shù)裝飾器,就是接受函數(shù)作為參數(shù),并對函數(shù)參數(shù)做一些包裝,然后返回增加了包裝的函數(shù),即生成了一個新函數(shù)。
讓我們看看下面這個例子:
def decorator_func(some_func): # define another wrapper function which modifies some_func def wrapper_func(): print("Wrapper function started") some_func() print("Wrapper function ended") return wrapper_func # Wrapper function add something to the passed function and decorator returns the wrapper function def say_hello(): print ("Hello") say_hello = decorator_func(say_hello) say_hello() # Output: # Wrapper function started # Hello # Wrapper function ended
上面例子中,decorator_func 就是定義的裝飾器函數(shù),它接受some_func作為參數(shù)。它定義了一個wrapper_func函數(shù),該函數(shù)調(diào)用了some_func但也增加了一些自己的代碼。
上面代碼中使用裝飾器的方法看起來有點復(fù)雜,其實真正的裝飾器的Python語法是這樣的:
裝飾器的Python語法
@decorator_func def say_hi(): print 'Hi!'
@ 符合是裝飾器的語法糖,在定義函數(shù)say_hi時使用,避免了再一次的賦值語句。
上面的語句等同于:
def say_hi(): print 'Hi!' say_hi = decorator_func(say_hi)
裝飾器的順序
@a @b @c def foo(): print('foo') # 等同于: foo = a(b(c(foo)))
帶參數(shù)函數(shù)的裝飾器
def decorator_func(some_func): def wrapper_func(*args, **kwargs): print("Wrapper function started") some_func(*args, **kwargs) print("Wrapper function ended") return wrapper_func @decorator_func def say_hi(name): print ("Hi!" + name)
上面代碼中,say_hi函數(shù)帶有一個參數(shù)。通常情況下,不同功能的函數(shù)可以有不同類別、不同數(shù)量的參數(shù),在寫wrapper_func的時候,我們不確定參數(shù)的名稱和數(shù)量,可以通過*args 和 **kwargs 來引用函數(shù)參數(shù)。
帶參數(shù)的裝飾器
不僅被裝飾的函數(shù)可以帶參數(shù),裝飾器本身也可以帶參數(shù)。參考下面的例子:
def use_logging(level): def decorator(func): def wrapper(*args, **kwargs): if level == "warn": logging.warn("%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)
簡單來說,帶參數(shù)的裝飾器就是在沒有參數(shù)的裝飾器外面再嵌套一個參數(shù)的函數(shù),該函數(shù)返回那個無參數(shù)裝飾器即可。
類作為裝飾器
前面我們提到裝飾器是可調(diào)用對象。在Python里面,除了函數(shù),類也是可調(diào)用對象。使用類裝飾器,優(yōu)點是靈活性大,高內(nèi)聚,封裝性。通過實現(xiàn)類內(nèi)部的__call__方法,當使用 @ 語法糖把裝飾器附加到函數(shù)上時,就會調(diào)用此方法。
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 say_hi(): print('Hi!') say_hi() # Output: # class decorator running # Hi! # class decorator ending
functools.wraps
使用裝飾器極大地復(fù)用了代碼,但是他有一個缺點就是原函數(shù)的元信息不見了,比如函數(shù)的docstring、__name__、參數(shù)列表,先看看下面例子:
def decorator_func(some_func): def wrapper_func(*args, **kwargs): print("Wrapper function started") some_func(*args, **kwargs) print("Wrapper function ended") return wrapper_func @decorator_func def say_hi(name): '''Say hi to somebody''' print ("Hi!" + name) print(say_hi.__name__) # Output: wrapper_func print(say_hi.__doc__) # Output: None
可以看到,say_hi函數(shù)被wrapper_func函數(shù)取代,它的__name__ 和 docstring 也自然是wrapper_func函數(shù)的了。
不過不用擔心,Python有functools.wraps,wraps本身也是一個裝飾器,它的作用就是把原函數(shù)的元信息拷貝到裝飾器函數(shù)中,使得裝飾器函數(shù)也有和原函數(shù)一樣的元信息。
from functools import wraps def decorator_func(some_func): @wraps(func) def wrapper_func(*args, **kwargs): print("Wrapper function started") some_func(*args, **kwargs) print("Wrapper function ended") return wrapper_func @decorator_func def say_hi(name): '''Say hi to somebody''' print ("Hi!" + name) print(say_hi.__name__) # Output: say_hi print(say_hi.__doc__) # Output: Say hi to somebody
類的內(nèi)置裝飾器
- 類屬性@property
- 靜態(tài)方法@staticmethod
- 類方法@classmethod
通常,我們需要先實例化一個類的對象,再調(diào)用其方法。
若類的方法使用了@staticmethod或@classmethod,就可以不需要實例化,直接類名.方法名()來調(diào)用。
從使用上來看,@staticmethod不需要指代自身對象的self或指代自身類的cls參數(shù),就跟使用普通函數(shù)一樣。
@classmethod不需要self參數(shù),但第一個參數(shù)必須是指代自身類的cls參數(shù)。如果在@staticmethod中要調(diào)用到這個類的一些屬性方法,只能直接類名.屬性名,或類名.方法名的方式。
而@classmethod因為持有cls參數(shù),可以來調(diào)用類的屬性,類的方法,實例化對象等。
總結(jié)
通過認識Python的函數(shù),我們逐步弄清了裝飾器的來龍去脈。裝飾器是代碼復(fù)用的好工具,在編程過程中可以在適當?shù)膱鼍坝枚喽嗍褂谩?br />
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
tkinter如何獲取復(fù)選框(Checkbutton)的值
這篇文章主要介紹了tkinter如何獲取復(fù)選框(Checkbutton)的值問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01淺談keras的深度模型訓(xùn)練過程及結(jié)果記錄方式
今天小編就為大家分享一篇淺談keras的深度模型訓(xùn)練過程及結(jié)果記錄方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-01-01Python爬蟲包 BeautifulSoup 遞歸抓取實例詳解
這篇文章主要介紹了Python爬蟲包 BeautifulSoup 遞歸抓取實例詳解的相關(guān)資料,需要的朋友可以參考下2017-01-01Python 模擬員工信息數(shù)據(jù)庫操作的實例
下面小編就為大家?guī)硪黄狿ython 模擬員工信息數(shù)據(jù)庫操作的實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10Jupyter Notebook讀入csv文件時出錯的解決方案
這篇文章主要介紹了Jupyter Notebook讀入csv文件時出錯的解決方案,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03Django后端接收嵌套Json數(shù)據(jù)及解析詳解
這篇文章主要介紹了Django后端接收嵌套Json數(shù)據(jù)及解析詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-07-07