摘要:一般情況下,我們使用裝飾器提供的語法糖,來簡化上面的寫法像上面的情況,可以動態修改函數或類功能的函數就是裝飾器。本文標題為會打扮的裝飾器本文鏈接為參考資料修飾器的函數式編程中的裝飾器介紹思誠之道裝飾器入門與提高賴明星
裝飾器
我們知道,在 Python 中,我們可以像使用變量一樣使用函數:
函數可以被賦值給其他變量
函數可以被刪除
可以在函數里面再定義函數
函數可以作為參數傳遞給另外一個函數
函數可以作為另一個函數的返回
簡而言之,函數就是一個對象。
對一個簡單的函數進行裝飾為了更好地理解裝飾器,我們先從一個簡單的例子開始,假設有下面的函數:
def hello(): return "hello world"
現在我們想增強 hello() 函數的功能,希望給返回加上 HTML 標簽,比如 hello world,但是有一個要求,不改變原來 hello() 函數的定義。這里當然有很多種方法,下面給出一種跟本文相關的方法:
def makeitalic(func): def wrapped(): return "" + func() + "" return wrapped
在上面的代碼中,我們定義了一個函數 makeitalic,該函數有一個參數 func,它是一個函數;在 makeitalic 函數里面我們又定義了一個內部函數 wrapped,并將該函數作為返回。
現在,我們就可以不改變 hello() 函數的定義,給返回加上 HTML 標簽了:
>>> hello = makeitalic(hello) # 將 hello 函數傳給 makeitalic >>> hello() "hello world"
在上面,我們將 hello 函數傳給 makeitalic,再將返回賦給 hello,此時調用 hello() 就得到了我們想要的結果。
不過要注意的是,由于我們將 makeitalic 的返回賦給了 hello,此時 hello() 函數仍然存在,但是它的函數名不再是 hello 了,而是 wrapped,正是 makeitalic 返回函數的名稱,可以驗證一下:
>>> hello.__name__ "wrapped"
對于這個小瑕疵,后文將會給出解決方法。
現在,我們梳理一下上面的例子,為了增強原函數 hello 的功能,我們定義了一個函數,它接收原函數作為參數,并返回一個新的函數,完整的代碼如下:
def makeitalic(func): def wrapped(): return "" + func() + "" return wrapped def hello(): return "hello world" hello = makeitalic(hello)
事實上,makeitalic 就是一個裝飾器(decorator),它『裝飾』了函數 hello,并返回一個函數,將其賦給 hello。
一般情況下,我們使用裝飾器提供的 @ 語法糖(Syntactic Sugar),來簡化上面的寫法:
def makeitalic(func): def wrapped(): return "" + func() + "" return wrapped @makeitalic def hello(): return "hello world"
像上面的情況,可以動態修改函數(或類)功能的函數就是裝飾器。本質上,它是一個高階函數,以被裝飾的函數(比如上面的 hello)為參數,并返回一個包裝后的函數(比如上面的 wrapped)給被裝飾函數(hello)。
裝飾器的使用形式裝飾器的一般使用形式如下:
@decorator def func(): pass
等價于下面的形式:
def func(): pass func = decorator(func)
裝飾器可以定義多個,離函數定義最近的裝飾器先被調用,比如:
@decorator_one @decorator_two def func(): pass
等價于:
def func(): pass func = decorator_one(decorator_two(func))
裝飾器還可以帶參數,比如:
@decorator(arg1, arg2) def func(): pass
等價于:
def func(): pass func = decorator(arg1, arg2)(func)
下面我們再看一些具體的例子,以加深對它的理解。
對帶參數的函數進行裝飾前面的例子中,被裝飾的函數 hello() 是沒有帶參數的,我們看看被裝飾函數帶參數的情況。對前面例子中的 hello() 函數進行改寫,使其帶參數,如下:
def makeitalic(func): def wrapped(*args, **kwargs): ret = func(*args, **kwargs) return "" + ret + "" return wrapped @makeitalic def hello(name): return "hello %s" % name @makeitalic def hello2(name1, name2): return "hello %s, %s" % (name1, name2)
由于函數 hello 帶參數,因此內嵌包裝函數 wrapped 也做了一點改變:
內嵌包裝函數的參數傳給了 func,即被裝飾函數,也就是說內嵌包裝函數的參數跟被裝飾函數的參數對應,這里使用了 (*args, **kwargs),是為了適應可變參數。
看看使用:
>>> hello("python") "hello python" >>> hello2("python", "java") "hello python, java"帶參數的裝飾器
上面的例子,我們增強了函數 hello 的功能,給它的返回加上了標簽 ...,現在,我們想改用標簽 ... 或
...
。是不是要像前面一樣,再定義一個類似 makeitalic 的裝飾器呢?其實,我們可以定義一個函數,將標簽作為參數,返回一個裝飾器,比如:def wrap_in_tag(tag): def decorator(func): def wrapped(*args, **kwargs): ret = func(*args, **kwargs) return "<" + tag + ">" + ret + "" + tag + ">" return wrapped return decorator
現在,我們可以根據需要生成想要的裝飾器了:
makebold = wrap_in_tag("b") # 根據 "b" 返回 makebold 生成器 @makebold def hello(name): return "hello %s" % name >>> hello("world") "hello world"
上面的形式也可以寫得更加簡潔:
@wrap_in_tag("b") def hello(name): return "hello %s" % name
這就是帶參數的裝飾器,其實就是在裝飾器外面多了一層包裝,根據不同的參數返回不同的裝飾器。
多個裝飾器現在,讓我們來看看多個裝飾器的例子,為了簡單起見,下面的例子就不使用帶參數的裝飾器。
def makebold(func): def wrapped(): return "" + func() + "" return wrapped def makeitalic(func): def wrapped(): return "" + func() + "" return wrapped @makebold @makeitalic def hello(): return "hello world"
上面定義了兩個裝飾器,對 hello 進行裝飾,上面的最后幾行代碼相當于:
def hello(): return "hello world" hello = makebold(makeitalic(hello))
調用函數 hello:
>>> hello() "hello world"基于類的裝飾器
前面的裝飾器都是一個函數,其實也可以基于類定義裝飾器,看下面的例子:
class Bold(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): return "" + self.func(*args, **kwargs) + "" @Bold def hello(name): return "hello %s" % name >>> hello("world") "hello world"
可以看到,類 Bold 有兩個方法:
__init__():它接收一個函數作為參數,也就是被裝飾的函數
__call__():讓類對象可調用,就像函數調用一樣,在調用被裝飾函數時被調用
還可以讓類裝飾器帶參數:
class Tag(object): def __init__(self, tag): self.tag = tag def __call__(self, func): def wrapped(*args, **kwargs): return "<{tag}>{res}{tag}>".format( res=func(*args, **kwargs), tag=self.tag ) return wrapped @Tag("b") def hello(name): return "hello %s" % name
需要注意的是,如果類裝飾器有參數,則 __init__ 接收參數,而 __call__ 接收 func。
裝飾器的副作用前面提到,使用裝飾器有一個瑕疵,就是被裝飾的函數,它的函數名稱已經不是原來的名稱了,回到最開始的例子:
def makeitalic(func): def wrapped(): return "" + func() + "" return wrapped @makeitalic def hello(): return "hello world"
函數 hello 被 makeitalic 裝飾后,它的函數名稱已經改變了:
>>> hello.__name__ "wrapped"
為了消除這樣的副作用,Python 中的 functool 包提供了一個 wraps 的裝飾器:
from functools import wraps def makeitalic(func): @wraps(func) # 加上 wraps 裝飾器 def wrapped(): return "" + func() + "" return wrapped @makeitalic def hello(): return "hello world" >>> hello.__name__ "hello"小結
本質上,裝飾器就是一個返回函數的高階函數。
裝飾器可以動態地修改一個類或函數的功能,通過在原有的類或者函數上包裹一層修飾類或修飾函數實現。
事實上,裝飾器就是閉包的一種應用,但它比較特別,接收被裝飾函數為參數,并返回一個函數,賦給被裝飾函數,閉包則沒這種限制。
參考資料本文由 funhacks 發表于個人博客,采用 Creative Commons BY-NC-ND 4.0(自由轉載-保持署名-非商用-禁止演繹)協議發布。
非商業轉載請注明作者及出處。商業轉載請聯系作者本人。
本文標題為: Python: 會打扮的裝飾器
本文鏈接為: https://funhacks.net/2016/11/...
Python修飾器的函數式編程 - coolshell
How can I make a chain of function decorators in Python? - Stack Overflow
Python中的裝飾器介紹 – 思誠之道
python裝飾器入門與提高 | 賴明星
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/44297.html
摘要:裝飾器的使用符合了面向對象編程的開放封閉原則。三簡單的裝飾器基于上面的函數執行時間的需求,我們就手寫一個簡單的裝飾器進行實現。函數體就是要實現裝飾器的內容。類裝飾器的實現是調用了類里面的函數。類裝飾器的寫法比我們裝飾器函數的寫法更加簡單。 目錄 前言 一、什么是裝飾器 二、為什么要用裝飾器 ...
摘要:變量查找規則在中一個變量的查找順序是局部環境,閉包,全局,內建閉包引用了自由變量的函數。閉包的作用閉包的最大特點是可以將父函數的變量與內部函數綁定,并返回綁定變量后的函數,此時即便生成閉包的環境父函數已經釋放,閉包仍然存在。 導語:本文章記錄了本人在學習Python基礎之函數篇的重點知識及個人心得,打算入門Python的朋友們可以來一起學習并交流。 本文重點: 1、掌握裝飾器的本質、功...
摘要:初步認識裝飾器函數裝飾器用于在源代碼中標記函數,以某種方式增強函數的行為。函數裝飾器在導入模塊時立即執行,而被裝飾的函數只在明確調用時運行。只有涉及嵌套函數時才有閉包問題。如果想保留函數原本的屬性,可以使用標準庫中的裝飾器。 《流暢的Python》筆記本篇將從最簡單的裝飾器開始,逐漸深入到閉包的概念,然后實現參數化裝飾器,最后介紹標準庫中常用的裝飾器。 1. 初步認識裝飾器 函數裝飾...
摘要:希望引以為戒鄭傳裝飾模式如果你了解,你肯定聽過裝飾器模式。在面向對象中,裝飾模式指動態地給一個對象添加一些額外的職責。就增加一些功能來說,裝飾模式比生成子類更為靈活。 漫談 如果作為一個Python入門,不了解Python裝飾器也沒什么,但是如果作為一個中級Python開發人員,如果再不對python裝飾器熟稔于心的話,那么可能并沒有量變積累到質變。 我以前也看過很多講python 裝...
閱讀 3937·2021-11-16 11:44
閱讀 3120·2021-11-12 10:36
閱讀 3378·2021-10-08 10:04
閱讀 1261·2021-09-03 10:29
閱讀 399·2019-08-30 13:50
閱讀 2609·2019-08-29 17:14
閱讀 1740·2019-08-29 15:32
閱讀 1085·2019-08-29 11:27