如何理解Python裝飾器?
python裝飾器的理解 - 天行健 - 博客頻道 - CSDN.NET
如果你是新手的話,可能會有很大的幫助。可以看我的這篇文章python裝飾器-代碼山
在語法層面支持aop
我是新手。看本回答前,請務必弄清閉包的含義。
我覺得下面的例子(相當於自帶參數的裝飾器可以實現的功能)會更好理解一些。
只要你懂閉包,這段代碼肯定看得懂。
def external_function(i, j):
k = i - j
def decorator(func):
def wrapper(*arg):
print this func is %s%(func.__name__)
print k * func(*arg)
return wrapper
return decorator
def my_func(x,y):
return x + y
if __name__ == __main__:
external_function(10,5)(my_func)(10,2)
裝飾器(decorator)函數把目標函數(需要裝飾的函數)作為它的參數。
裝飾器里的wrapper(封裝函數)把目標函數的參數作為它的參數。
如果把上面的例子寫成真正的裝飾器,就是:
def external_function(i, j):
k = i - j
def decorator(func):
def wrapper(*arg):
print this func is %s%(func.__name__)
print k * func(*arg)
return wrapper
return decorator
@external_function(10,5)
def my_func(x,y):
return x + y
if __name__ == __main__:
my_func(10, 2)
上面代碼中的裝飾器語法糖 @xxx 中的 xxx 必須是個函數。
@xxx 的意思就是賦值語句 func = xxx(func)
func就是目標函數,也就是@xxx下面定義的函數。在這個例子里就是指my_func。
但這個語法糖 @xxx 必須寫在定義目標函數之前。
裝飾器的語法糖必須寫在定義目標函數之前,這在邏輯上很難接受。
因為不用語法糖,而用普通的賦值語句 func = xxx(func) ,代碼是這樣的:
def external_function(i, j):
k = i - j
def decorator(func):
def wrapper(*arg):
print this func is %s%(func.__name__)
print k * func(*arg)
return wrapper
return decorator
def my_func(x,y):
return x + y
my_func = external_function(10,5)(my_func)
if __name__ == __main__:
my_func(10,2)
看到沒,要用普通賦值語句的話,這條語句是在定義目標函數之後才能寫。
而@xxx 這個裝飾器語法糖得寫在前面。
-----------------------------未完----------------------------
-----------------------------------------------
我不喜歡評論,尤其是反面評論,因為我是無償回答問題,所以我不想再無償回複評論。
如果你覺得我的觀點不對,請自己寫個回答給答主,甚至你可以在裡面拿我做反例。
我不在意。
我用專欄里的一篇文章來答題。
專欄鏈接:給妹子講python,歡迎大家關注,提意見!
【要點搶先看】
1.神奇的裝飾器到底是什麼
2.裝飾器的用法和語法糖
3.裝飾器如何添加參數
裝飾器是python里的一個非常有意思的部分,他用於封裝函數代碼,顯式的將封裝器應用到被封裝的函數上,從而使得他們選擇加入到裝飾器指定的功能中。對於在函數運行前處理常見前置條件(例如確認授權),或在函數運行後確保清理(輸出清除或異常處理),裝飾器都非常有用。
是不是感覺聽不明白,太繞了!
簡單來說,裝飾器就是實現了一個通用的功能,然後將這個通用的功能應用到不同的、需要使用這個功能的函數上,從而避免每次都在不同函數上反覆寫相同的功能代碼。
裝飾器的本質是一個函數,他接受被裝飾的函數作為位置參數,裝飾器通過使用該參數來執行某些操作,然後返回一個函數引用,這個函數可以是原始函數,或者是另外一個函數。
我們舉例子說明,裝飾器是這樣的函數,他們接受被裝飾的可調用函數作為唯一的參數,並且返回一個可調用函數,
registry = []
def register(decorated):
registry.append(decorated)
return decorated
def foo():
return 3
foo = register(foo)
print(registry[0])
& register方法是一個簡單的裝飾器,它把被裝飾的函數添加到一個列表中,然後這裡是將未改變的被裝飾函數返回,可以看出,裝飾器一般是傳入被裝飾函數的引用,然後經過一些指定的處理,最後返回值也是一個函數引用。 還有一種更簡單的語法形式: 裝飾器的語法糖:我們這裡看到的對foo進行裝飾的方法是運用 foo = register(foo)語句,還有一種簡單的用法是在聲明函數的位置應用裝飾器,從而使得代碼更容易閱讀,並且讓人立刻意識到使用了裝飾器 @register @register for func in registry: 3 再看一個更複雜、更一般化的裝飾器函數。裝飾器的本質是在執行原有函數(被裝飾的函數)的同時,再加上一些額外的功能。 在這個裝飾器函數requires_ints我們可以看到,他定義了一個內嵌函數inner,這個內嵌函數的參數首先收集被裝飾函數的所有參數,然後對其進行判斷,判斷其是否為整數類型(這就是裝飾器添加的額外功能),然後再調用被裝飾的函數本身,最後將這個內嵌函數返回。因此當我們再用原函數名進行調用的時候,原來的被裝飾函數的引用就能指向這個新的內嵌函數,就能在實現原函數功能的基礎上,加上附加的功能了。 同時,我們再提煉一下這裡面的幾個重難點: 第一,requires_ints中,decorated這個變數是內嵌作用域的變數,在他調用退出後,返回的inner函數是可以記住這個變數的。 第二,python不支持函數的參數列表的多態,即一個函數名只能對應唯一的參數列表形式。 第三,在內嵌函數內部調用被裝飾函數的時候,使用了解包參數,關於這*args, **kwargs,的參數形式,前面章節中細講過。 那我們也用這個裝飾器來裝飾一個函數。 foo(3,5) 8 這裡將名稱foo賦給inner函數,而不是賦給原來被定義的函數,如果運行foo(3,5),將利用傳入的這兩個參數運行inner函數,inner函數執行類型檢查,然後運行被裝飾方法,如果傳入的不是整形數,例如下面這個例子,那麼裝飾器的附加功能就會進行類型檢查: foo(a,5) Traceback (most recent call last): 其次內嵌的函數和被裝飾的函數的參數形式必須完全一樣,這裡用的*args, **kwargs概況函數參數的一般形式,因此也是完全對應的。 最後說說裝飾器參數 最後來介紹這個複雜一些的話題,裝飾器參數。之前我們列舉的常規例子里,裝飾器只有一個參數,就是被裝飾的方法。但是,有時讓裝飾器自身帶有一些需要的信息,從而使裝飾器可以用恰當的方式裝飾方法十分有用。 這些參數並不是和被裝飾的函數並列作為參數簽名,而是在原有裝飾器的基礎上額外再增加一層封裝,那麼,實質是這個接受其他參數的裝飾器並不是一個實際的裝飾器,而是一個返回裝飾器的函數。 最終返回的內嵌函數inner是最終使用indent和sort_keys參數的函數,這沒有問題 def json_output(indent=None, sort_keys=False): @json_output(indent=8) print(do_nothing()) { 我們在這裡詳細解釋說明的是操作順序,看上去我們使用的是@json_output(indent=8),作這和之前的裝飾器語法糖看上去有些不同,實際上這個不是最終的裝飾器函數,通過調用json_output(indent=8),返回函數指針actual_decorator,這個函數才是真正放在@後的裝飾器函數,原始的被裝飾函數最終獲得了內涵更豐富的inner函數對象,完成了裝飾過程,值得一提的是,所謂的裝飾器參數最終傳給了最內層的inner函數。 記住,在定義裝飾器函數後,真正的裝飾器函數只有一個參數,那就是被裝飾的函數指針,而有其他參數的函數實質上只是裝飾器的外圍函數,他可以依據參數對裝飾器進行進一步的定製。一句話:一個函數不可能接受被裝飾的方法,又接受其他參數 在語法糖中@func這種不帶括弧的,就是直接使用裝飾器函數進行裝飾,如果是@func()帶括弧的,實質上是先調用func()函數返回真正的裝飾器,然後再用@進行調用。 @劉志軍 @黃哥 @蕭井陌 @廖雪峰 雖然是編程新手,也嘗試來答一個,選自我的博客
registry = []
def register(decorated):
registry.append(decorated)
return decorated
def foo(x=3):
return x
def bar(x=5):
return 5
print(func())
5
def requires_ints(decorated):
def inner(*args, **kwargs):
kwarg_values = [i for i in kwargs.values()]
for arg in list(args) + kwarg_values:
if not isinstance(arg, int):
raise TypeError({} only accepts integers as arguments.format(decorated.__name__))
return decorated(*args, **kwargs)
return inner
@requires_ints
def foo(x,y):
print(x+y)
@requires_ints
def foo(x,y):
print(x+y)
File "E:/12homework/12homework.py", line 15, in &
foo(a,5)
File "E:/12homework/12homework.py", line 7, in inner
raise TypeError({} only accepts integers as arguments.format(decorated.__name__))
TypeError: foo only accepts integers as arguments
import json
def actual_decorator(decorated):
def inner(*args, **kwargs):
result = decorated(*args, **kwargs)
return json.dumps(result, indent=indent, sort_keys=sort_keys)
return inner
return actual_decorator
def do_nothing():
return {status:done,func:yes}
"status": "done",
"func": "yes"
}
裝飾器的定義
在不同語境下,裝飾器有不一樣的含義,我大致認為有3種定義:
- 一種把另一個對象當參數的對象
- 一種語法糖
- 面對對象設計中的裝飾器模式
之所以這裡特別指出,是因為在很多文章和書中
把裝飾器定義成一個函數,有些更擴展一些,把裝飾器定義成一個callable對象對剛開始學習的讀者來說這麼解釋或許不錯,但在使用python的過程中,我們發現裝飾器可以是- 函數
- 類 (例:
classmethod
和property
) - 類方法 (例:
FLask
類的route
)
如果說類方法有__call__
方法,能符合上面callable對象的定義的話
__call__
方法,所以我認為更準確的定義是我上面說的第1,2點
裝飾器的作用
廣義上說,更優雅地處理對象,相比taget = decorate(target)
,@decorate
可要優雅多了
- 禁止函數運行 (剛想到,寫個不return原函數的裝飾器去裝飾某函數,這個函數不就不運行了嘛,不曉得是否有人會這麼干)
- 記錄函數的運行狀況
- 緩存計算結果
- 檢查/修改參數
- 裝逼?
- 其他
一句話總結:
a被b裝飾,a函數就等同於,以a做參數b返回的函數
翻譯一下:
a(函數調用,帶括弧)被b(@b)裝飾,a(函數名)就等同於,以a(函數名,不加括弧)做參數b(函數調用,帶括弧)返回的函數(函數名,不加括弧)
裝飾器本質上也是函數,接受函數對象來作為參數,並在裝飾器的內部來調用接受的函數對象完成相關的函數調用。也可以這樣理解,為了方便在幾個不同函數調用之前或者之後完成相關的統一操作,注意是完成統一的操作,可以傳參數使得裝飾器不完全一樣,後面會講到。最重要的應用如工程應用上記錄相關的內部調用介面的流水日誌,不同的介面需要統一的樣式,所以可以用裝飾器來實現。先簡單看一下示例:
from time import ctime
def deco(func):
def decorator(*args, **kwargs):
print([%s] %s() is called % (ctime(), func.__name__))
return func(*args, **kwargs)
return decorator
@deco
def foo():
print(Hello, Python)
foo()
在如上示例中,定義了一個裝飾器,其中參數func需要函數的對象,返回值是decorator函數,其中decorator函數的返回值正是func的返回值。該裝飾器的功能就是在函數調用之前,列印了函數調用的時間和函數名。
裝飾器的使用過程很簡單,通過註解@符號標註一下即可。這本質上相當於foo = deco(foo) 的嵌套調用。
這裡面,你又遇到了 *args 和 **kwargs,它們可以組合接收任意函數參數。不理解的可以翻看Python 中的 *args 和 **kwargs 。
裝飾器也可以堆疊起來,即對某個函數使用多個裝飾器,比如:
from time import ctime
def deco1(func):
def decorator1(*args, **kwargs):
print([%s] %s() is called % (ctime(), func.__name__))
return func(*args, **kwargs)
return decorator1
def deco2(func):
def decorator2(*args, **kwargs):
print([%s] %s() is called % (ctime(), func.__name__))
return func(*args, **kwargs)
return decorator2
@deco2
@deco1
def foo():
print(Hello, Python)
foo()
會輸出什麼呢?可以先分析一下。
運行一下,輸出如下:
[Fri Jul 21 15:15:53 2017] decorator1() is called
[Fri Jul 21 15:15:53 2017] foo() is called
Hello, Python
是否跟你想的一樣?在嵌套調用的過程中,foo = deco2(deco1(foo)),所以先返回 deco1(foo) 的函數名字即 decorator1, 後返回 foo 的函數名。
裝飾器本身也可以傳入參數,使得在統一的過程中帶點奇特的色彩,如:
from time import ctime
def deco(tag):
def decorator(func):
def wrapper(*args, **kw):
print([%s] %s() is called, Tag is %s. % (ctime(), func.__name__, tag))
return func(*args, **kw)
return wrapper
return decorator
@deco(Python)
def foo():
print(Hello, Python)
@deco(Java)
def bar():
print(Hello, Python)
foo()
bar()
讓我們簡單分析下這個裝飾器,deco函數接受的是一個str對象tag,當執行deco(Python) 後返回的是decorator函數,此函數需要接受一個函數對象,同時返回wrapper函數,而wrapper函數的結果就是func函數的返回值。說的可能有點繞,但理一下會覺得非常簡單。
最後說明一下的是,由於加入了裝飾器,函數的__name__和__doc__等信息都發生了變化:
from time import ctime
def deco(func):
def decorator(*args, **kwargs):
decorator for func
print([%s] %s() is called % (ctime(), func.__name__))
return func(*args, **kwargs)
return decorator
@deco
def foo():
function: foo
print(Hello, Python)
foo.__name__
foo.__doc__
運行的結果如下:
decorator
decorator for func
由此可見,加入裝飾器改變了函數內部的相關屬性。如何避免此問題呢?Python中有專門的包來避免這種轉換:functools.wraps。示例如下:
from time import ctime
import functools
def deco(func):
@functools.wraps(func)
def decorator(*args, **kwargs):
decorator for func
print([%s] %s() is called % (ctime(), func.__name__))
return func(*args, **kwargs)
return decorator
@deco
def foo():
function: foo
print(Hello, Python)
foo.__name__
foo.__doc__
運行結果如下:
foo
function: foo
這樣就保留了原先函數的屬性。小編在實際的工作中一般也是加入此項功能的。
歡迎大家關注同名微信公眾號 Python那些事。
@劉志軍 講解使得我從對裝飾器模糊的理解狀態,進階到了感覺十分清晰;我似乎感受到了python裝飾器的本質是,或者說主要描述方式是,通過內嵌函數的方式,並且返回的是函數的定義,對於最裡面的函數,無需出現return關鍵詞,也能實現@形式的註解;
為什麼要使用@ 形式的註解表示裝飾器呢?是為了代碼簡單地重用。而python如何實現可以以@形式的註解呢,是通過內嵌函數的形式,返回的是函數的定義,注意這裡返回的是函數定義,而不是已經把函數執行了。
有一種重寫了函數的定義感覺。
裝飾器設計模式就是增強方法的功能,比如說在方法執行前做點什麼,在方法執行後再做點什麼。
Python 的高階函數簡單理解成可以返回函數的函數。所以Python的裝飾器就可以理解成具備裝飾器(模式)的高階函數。先把裝飾器模式和高階函數理解了,就可以理解Python的裝飾器了。當然還有可以有參數的裝飾器,修改.__name__等屬性這些細節再注意一下。其實說增強方法功能,不能說它就是語法層面的AOP,AOP的重點不在於產生代理去增強方法的功能,而在於定義切點,切面。李冬答主寫的很明了了
學代碼和日語的英文系,膜拜我也是學代碼和日語的會計系大二準備一會面壁去了今天剛看到裝飾器,寫一下自己的理解好了以下全是連偽代碼都不是的偽偽代碼用書上例子的實現來:假如我寫了一個好厲害的函數&>&>&>def 加法 (*arg): return 很厲害的過程現在我想要在他運行的時候同時列印出函數名參數結果來進行測試那麼我只要把函數改成&>&>&>def 加法 (): 結果=很厲害的過程 print() return 很厲害的過程的結果這樣就好了那麼問題來了
如果我一高興寫了一百個醬的函數都要進行測試呢那這樣改豈不是很麻煩?而且如果我要單純的使用這些函數不需要在過程中列印這些信息呢?沒錯按照《python語言及其應用》所述python裝飾器可以使你在不修改源代碼的情況下,修改已經存在的函數那麼,為了搞一搞這100個函數我們寫個以函數為參數的函數,作為裝飾器&>&>&>def 裝飾器(函數): def 新函數(*arg): 結果=很厲害的過程 列印各種測試信息 return 很厲害的過程的結果 return 新函數好啦,那麼裝飾器就寫好了那麼如何使用這個裝飾器呢?有以下兩種方法
1.我們只需要來個賦值&>&>&>測試函數 = 裝飾器(加法)沒錯,我們已經把自己寫的「加法」函數,作為參數帶入了裝飾器這個函數里,之後&>&>&>測試函數(1, 2)就可以得到之後的結果(列印的各種測試信息3)第二種方法,在寫函數的時候艾特所以請在之前寫好裝飾器那麼先艾特一下我們的裝飾器咯&>&>&>@裝飾器· · · def 加法 (*arg):· · · return 很厲害的過程· · ·那麼我們在運行函數的時候,就可以&>&>&>加法(1, 2)列印的各種測試信息3噹噹當
這樣就可以省去我們重複寫代碼的過程也減少了刪改代碼的次數應該是一個對出錯的很好的內部控制吧那麼為什麼上面的裝飾器用了雙層函數呢外層函數 裝飾器的參數是我們要裝飾的函數而內層函數 新函數的參數,則我們要裝飾的函數的參數,也就是原函數所要處理的參數那麼為什麼要內層使用閉包呢?其實我也是今天看到的閉包說實話不是很懂,自己測試了一下沒有閉包的結果是字元串之類的對象類型閉包的結果的對象類型則是函數具體的呢,寶寶也不懂,成了老司機再說成了老司機還有人問我再說獻醜了以上話說我有答歪嗎。。。推薦閱讀:
※用Python做地圖投影 - 多面孔的世界
※Python到底是個啥?
※【Python基礎教程】閱讀&實驗報告〖一〗
※為什麼在Python中沒有專門的char數據類型呢?
※python初學者,如果你連這樣的習題寫不出代碼,該怎麼辦?