摘要:使用類裝飾器,優點是靈活性大,高內聚,封裝性。不過不用擔心,有,本身也是一個裝飾器,它的作用就是把原函數的元信息拷貝到裝飾器函數中,使得裝飾器函數也有和原函數一樣的元信息。
Python的裝飾器(decorator)是一個很棒的機制,也是熟練運用Python的必殺技之一。裝飾器,顧名思義,就是用來裝飾的,它裝飾的是一個函數,保持被裝飾函數的原有功能,再裝飾上(添油加醋)一些其它功能,并返回帶有新增功能的函數對象,所以裝飾器本質上是一個返回函數對象的函數(確切的說,裝飾器應該是可調用對象,除了函數,類也可以作為裝飾器)。
在編程過程中,我們經常遇到這樣的場景:登錄校驗,權限校驗,日志記錄等,這些功能代碼在各個環節都可能需要,但又十分雷同,通過裝飾器來抽象、剝離這部分代碼可以很好解決這類場景。
裝飾器是什么?
要理解Python的裝飾器,首先我們先理解一下Python的函數對象。我們知道,在Python里一切都是對象,函數也不例外,函數是第一類對象(first-class objects),它可以賦值給變量,也可以作為list的元素,還可以作為參數傳遞給其它函數。
函數可以被變量引用
定義一個簡單的函數:
def say_hi(): print("Hi!") say_hi() # Output: Hi!
我們可以通過另外一個變量say_hi2來引用say_hi函數:
say_hi2 = say_hi print(say_hi2) # Output:say_hi2() # Output: Hi!
上面的語句中say_hi2 和 say_hi 指向了同樣的函數定義,二者的執行結果也相同。
函數可以作為參數傳遞給其它函數
def say_more(say_hi_func): print("More") say_hi_func() say_more(say_hi) # Output: # More # Hi
在上面的例子中,我們把say_hi函數當做參數傳遞給say_more函數,say_hi 被變量 say_hi_func 引用。
函數可以定義在其它函數內部
def say_hi(): print("Hi!") def say_name(): print("Tom") say_name() say_hi() # Output: # Hi! # Tom say_name() # 報錯
上述代碼中,我們在say_hi()函數內部定義了另外一個函數say_name()。say_name()只在say_hi函數內部可見(即,它的作用域在say_hi函數內部),在say_hi外包調用時就會出錯。
函數可以返回其它函數的引用
def say_hi(): print("Hi!") def say_name(): print("Tom") return say_name say_name_func = say_hi() # 打印Hi!,并返回say_name函數對象 # 并賦值給say_name_func say_name_func() # 打印 Tom
上面的例子,say_hi函數返回了其內部定義的函數say_name的引用。這樣在say_hi函數外部也可以使用say_name函數了。
前面我們理解了函數,這有助于我們接下來弄明白裝飾器。
裝飾器(Decorator)
裝飾器是可調用對象(callable objects),它用來修改函數或類。
可調用對象就是可以接受某些參數并返回某些對象的對象。Python里的函數和類都是可調用對象。
函數裝飾器,就是接受函數作為參數,并對函數參數做一些包裝,然后返回增加了包裝的函數,即生成了一個新函數。
讓我們看看下面這個例子:
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 就是定義的裝飾器函數,它接受some_func作為參數。它定義了一個wrapper_func函數,該函數調用了some_func但也增加了一些自己的代碼。
上面代碼中使用裝飾器的方法看起來有點復雜,其實真正的裝飾器的Python語法是這樣的:
裝飾器的Python語法
@decorator_func def say_hi(): print "Hi!"
@ 符合是裝飾器的語法糖,在定義函數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)))
帶參數函數的裝飾器
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函數帶有一個參數。通常情況下,不同功能的函數可以有不同類別、不同數量的參數,在寫wrapper_func的時候,我們不確定參數的名稱和數量,可以通過args 和 *kwargs 來引用函數參數。
帶參數的裝飾器
不僅被裝飾的函數可以帶參數,裝飾器本身也可以帶參數。參考下面的例子:
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)
簡單來說,帶參數的裝飾器就是在沒有參數的裝飾器外面再嵌套一個參數的函數,該函數返回那個無參數裝飾器即可。
類作為裝飾器
前面我們提到裝飾器是可調用對象。在Python里面,除了函數,類也是可調用對象。使用類裝飾器,優點是靈活性大,高內聚,封裝性。通過實現類內部的__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 say_hi(): print("Hi!") say_hi() # Output: # class decorator running # Hi! # class decorator ending functools.wraps
使用裝飾器極大地復用了代碼,但是他有一個缺點就是原函數的元信息不見了,比如函數的docstring、__name__、參數列表,先看看下面例子:
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函數被wrapper_func函數取代,它的__name__ 和 docstring 也自然是wrapper_func函數的了。
不過不用擔心,Python有functools.wraps,wraps本身也是一個裝飾器,它的作用就是把原函數的元信息拷貝到裝飾器函數中,使得裝飾器函數也有和原函數一樣的元信息。
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
類的內置裝飾器
類屬性@property
靜態方法@staticmethod
類方法@classmethod
通常,我們需要先實例化一個類的對象,再調用其方法。
若類的方法使用了@staticmethod或@classmethod,就可以不需要實例化,直接類名.方法名()來調用。
從使用上來看,@staticmethod不需要指代自身對象的self或指代自身類的cls參數,就跟使用普通函數一樣。@classmethod不需要self參數,但第一個參數必須是指代自身類的cls參數。如果在@staticmethod中要調用到這個類的一些屬性方法,只能直接類名.屬性名,或類名.方法名的方式。
而@classmethod因為持有cls參數,可以來調用類的屬性,類的方法,實例化對象等。
總結
通過認識Python的函數,我們逐步弄清了裝飾器的來龍去脈。裝飾器是代碼復用的好工具,在編程過程中可以在適當的場景用多多使用。
我在我的個人博客“猿人學網站”和公眾號“猿人學Python”上寫Python教程,有興趣的可以關注公眾號和網站。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/43643.html
摘要:裝飾器是可調用的對象,其參數是另一個函數被裝飾的函數。第二大特性是,裝飾器在加載模塊時立即執行。另一個常見的裝飾器是,它的作用是協助構建行為良好的裝飾器。 裝飾器是可調用的對象,其參數是另一個函數(被裝飾的函數)。 裝飾器基礎知識 首先看一下這段代碼 def deco(fn): print I am %s! % fn.__name__ @deco def func(): ...
摘要:重寫內建名字空間中的函數閉包閉包是詞法閉包的簡稱。另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。 Python 中的 Decorator(裝飾器) 是對一個函數或者方法的封裝,從而使其可以完成一些與自身功能無關的工作。 預備知識 一切皆對象 在 Python 中,所有的一切都被視為對象,任何的變量、函數、類等都是 object 的子類。因此除了變量之外,函數和類等也可以...
摘要:下面我們一起拋去無關概念,簡單地理解下的裝飾器。用函數實現裝飾器裝飾器要求入參是函數對象,返回值是函數對象,嵌套函數完全能勝任。為了對調用方透明,裝飾器返回的對象要偽裝成被裝飾的函數。 來源:http://www.lightxue.com/under... ???????Python有大量強大又貼心的特性,如果要列個最受歡迎排行榜,那么裝飾器絕對會在其中。???????剛接觸裝飾器,會...
摘要:一般情況下,我們使用裝飾器提供的語法糖,來簡化上面的寫法像上面的情況,可以動態修改函數或類功能的函數就是裝飾器。本文標題為會打扮的裝飾器本文鏈接為參考資料修飾器的函數式編程中的裝飾器介紹思誠之道裝飾器入門與提高賴明星 裝飾器 我們知道,在 Python 中,我們可以像使用變量一樣使用函數: 函數可以被賦值給其他變量 函數可以被刪除 可以在函數里面再定義函數 函數可以作為參數傳遞給另外...
摘要:為了避免重復調用,可以適當地做緩存,的裝飾器可以完美的完成這一任務。這意味著我們可以為方法創建裝飾器,只是要記得考慮。裝飾器封裝了函數,這使得調試函數變得困難。另外,使用裝飾器去管理緩存和權限。 原文地址 之前用python簡單寫了一下斐波那契數列的遞歸實現(如下),發現運行速度很慢。 def fib_direct(n): assert n > 0, invalid n ...
閱讀 1034·2023-04-26 02:26
閱讀 2147·2021-09-26 10:16
閱讀 1553·2019-08-30 12:57
閱讀 3466·2019-08-29 16:10
閱讀 3222·2019-08-29 13:47
閱讀 1188·2019-08-29 13:12
閱讀 2139·2019-08-29 11:11
閱讀 1337·2019-08-26 13:28