摘要:下面我們一起拋去無關概念,簡單地理解下的裝飾器。用函數實現裝飾器裝飾器要求入參是函數對象,返回值是函數對象,嵌套函數完全能勝任。為了對調用方透明,裝飾器返回的對象要偽裝成被裝飾的函數。
來源:http://www.lightxue.com/under...
???????Python有大量強大又貼心的特性,如果要列個最受歡迎排行榜,那么裝飾器絕對會在其中。
???????剛接觸裝飾器,會覺得代碼不多卻難以理解。其實裝飾器的語法本身挺簡單的,復雜是因為同時混雜了其它的概念。下面我們一起拋去無關概念,簡單地理解下Python的裝飾器。
???????在解釋器下跑個裝飾器的例子,直觀地感受一下
# make_bold就是裝飾器,實現方式這里略去 >>> @make_bold ... def get_content(): ... return "hello world" ... >>> get_content() "hello world"
???????被make_bold裝飾的get_content,調用后返回結果會自動被b標簽包住。怎么做到的呢,簡單4步就能明白了。
1. 函數是對象???????我們定義個get_content函數。這時get_content也是個對象,它能做所有對象的操作。
它有id,有type,有值。
>>> id(get_content) 140090200473112 >>> type(get_content)>>> get_content
跟其他對象一樣可以被賦值給其它變量。
>>> func_name = get_content >>> func_name() "hello world"
它可以當參數傳遞,也可以當返回值
>>> def foo(bar): ... print(bar()) ... return bar ... >>> func = foo(get_content) hello world >>> func() "hello world"2. 自定義函數對象
???????我們可以用class來構造函數對象。有成員函數__call__的就是函數對象了,函數對象被調用時正是調用的__call__。
class FuncObj(object): def __init__(self, name): print("Initialize") self.name= name def __call__(self): print("Hi", self.name)
我們來調用看看??梢钥吹?,函數對象的使用分兩步:構造和調用(同學們注意了,這是考點)。
>>> fo = FuncObj("python") Initialize >>> fo() "Hi python"3. @是個語法糖
裝飾器的@沒有做什么特別的事,不用它也可以實現一樣的功能,只不過需要更多的代碼。
@make_bold def get_content(): return "hello world"
上面的代碼等價于下面的
def get_content(): return "hello world" get_content = make_bold(get_content)
4. 用類實現裝飾器make_bold是個函數,要求入參是函數對象,返回值是函數對象。@的語法糖其實是省去了上面最后一行代碼,使可讀性更好。用了裝飾器后,每次調用get_content,真正調用的是make_bold返回的函數對象。
入參是函數對象,返回是函數對象,如果第2步里的類的構造函數改成入參是個函數對象,不就正好符合要求嗎?我們來試試實現make_bold。
class make_bold(object): def __init__(self, func): print("Initialize") self.func = func def __call__(self): print("Call") return "{}".format(self.func())
大功告成,看看能不能用。
>>> @make_bold ... def get_content(): ... return "hello world" ... Initialize >>> get_content() Call "hello world"
成功實現裝飾器!是不是很簡單?
這里分析一下之前強調的構造和調用兩個過程。我們去掉@語法糖好理解一些。
# 構造,使用裝飾器時構造函數對象,調用了__init__ >>> get_content = make_bold(get_content) Initialize # 調用,實際上直接調用的是make_bold構造出來的函數對象 >>> get_content() Call "hello world"
到這里就徹底清楚了,完結撒花,可以關掉網頁了~~~(如果只是想知道裝飾器原理的話)
1. def的函數對象初始化閱讀源碼時,經常見到用嵌套函數實現的裝飾器,怎么理解?同樣僅需4步。
用class實現的函數對象很容易看到什么時候構造的,那def定義的函數對象什么時候構造的呢?
# 這里的全局變量刪去了無關的內容 >>> globals() {} >>> def func(): ... pass ... >>> globals() {"func":}
不像一些編譯型語言,程序在啟動時函數已經構造那好了。上面的例子可以看到,執行到def會才構造出一個函數對象,并賦值給變量make_bold。
這段代碼和下面的代碼效果是很像的。
class NoName(object): def __call__(self): pass func = NoName()2. 嵌套函數
Python的函數可以嵌套定義。
def outer(): print("Before def:", locals()) def inner(): pass print("After def:", locals()) return inner #inner是在outer內定義的,所以算outer的局部變量。 #執行到def inner時函數對象才創建,因此每次調用outer都會創建一個新的inner。 #下面可以看出,每次返回的inner是不同的。 >>> outer() Before def: {} After def: {"inner":3. 閉包.inner at 0x7f0b18fa0048>} .inner at 0x7f0b18fa0048> >>> outer() Before def: {} After def: {"inner": .inner at 0x7f0b18fa00d0>} .inner at 0x7f0b18fa00d0>
嵌套函數有什么特別之處?因為有閉包。
def outer(): msg = "hello world" def inner(): print(msg) return inner
下面的試驗表明,inner可以訪問到outer的局部變量msg。
>>> func = outer() >>> func() hello world
4. 用函數實現裝飾器閉包有2個特點
inner能訪問outer及其祖先函數的命名空間內的變量(局部變量,函數參數)。
調用outer已經返回了,但是它的命名空間被返回的inner對象引用,所以還不會被回收。
這部分想深入可以去了解Python的LEGB規則。
裝飾器要求入參是函數對象,返回值是函數對象,嵌套函數完全能勝任。
def make_bold(func): print("Initialize") def wrapper(): print("Call") return "{}".format(func()) return wrapper
用法跟類實現的裝飾器一樣。可以去掉@語法糖分析下構造和調用的時機。
>>> @make_bold ... def get_content(): ... return "hello world" ... Initialize >>> get_content() Call "hello world"
因為返回的wrapper還在引用著,所以存在于make_bold命名空間的func不會消失。make_bold可以裝飾多個函數,wrapper不會調用混淆,因為每次調用make_bold,都會有創建新的命名空間和新的wrapper。
到此函數實現裝飾器也理清楚了,完結撒花,可以關掉網頁了~~~(后面是使用裝飾的常見問題)
帶參數的裝飾器,有時會異常的好用。我們看個例子。
>>> @make_header(2) ... def get_content(): ... return "hello world" ... >>> get_content() "hello world
" #怎么做到的呢?其實這跟裝飾器語法沒什么關系。去掉@語法糖會變得很容易理解。 @make_header(2) def get_content(): return "hello world" # 等價于 def get_content(): return "hello world" unnamed_decorator = make_header(2) get_content = unnamed_decorator(get_content)
上面代碼中的unnamed_decorator才是真正的裝飾器,make_header是個普通的函數,它的返回值是裝飾器。
來看一下實現的代碼。
def make_header(level): print("Create decorator") # 這部分跟通常的裝飾器一樣,只是wrapper通過閉包訪問了變量level def decorator(func): print("Initialize") def wrapper(): print("Call") return "{1} ".format(level, func()) return wrapper # make_header返回裝飾器 return decorator
看了實現代碼,裝飾器的構造和調用的時序已經很清楚了。
>>> @make_header(2) ... def get_content(): ... return "hello world" ... Create decorator Initialize >>> get_content() Call "2. 如何裝飾有參數的函數?hello world
"
為了有條理地理解裝飾器,之前例子里的被裝飾函數有意設計成無參的。我們來看個例子。
@make_bold def get_login_tip(name): return "Welcome back, {}".format(name)
最直接的想法是把get_login_tip的參數透傳下去。
class make_bold(object): def __init__(self, func): self.func = func def __call__(self, name): return "{}".format(self.func(name))
???????如果被裝飾的函數參數是明確固定的,這么寫是沒有問題的。但是make_bold明顯不是這種場景。它既需要裝飾沒有參數的get_content,又需要裝飾有參數的get_login_tip。這時候就需要可變參數了。
class make_bold(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): return "{}".format(self.func(*args, **kwargs))
???????當裝飾器不關心被裝飾函數的參數,或是被裝飾函數的參數多種多樣的時候,可變參數非常合適。可變參數不屬于裝飾器的語法內容,這里就不深入探討了。
3. 一個函數能否被多個裝飾器裝飾?下面這么寫合法嗎?
@make_italic @make_bold def get_content(): return "hello world"
合法。上面的的代碼和下面等價,留意一下裝飾的順序。
def get_content(): return "hello world" get_content = make_bold(get_content) # 先裝飾離函數定義近的 get_content = make_italic(get_content)4. functools.wraps有什么用?
???????Python的裝飾器倍感貼心的地方是對調用方透明。調用方完全不知道也不需要知道調用的函數被裝飾了。這樣我們就能在調用方的代碼完全不改動的前提下,給函數patch功能。
???????為了對調用方透明,裝飾器返回的對象要偽裝成被裝飾的函數。偽裝得越像,對調用方來說差異越小。有時光偽裝函數名和參數是不夠的,因為Python的函數對象有一些元信息調用方可能讀取了。為了連這些元信息也偽裝上,functools.wraps出場了。它能用于把被調用函數的__module__,__name__,__qualname__,__doc__,__annotations__賦值給裝飾器返回的函數對象。
import functools def make_bold(func): @functools.wraps(func) def wrapper(*args, **kwargs): return "{}".format(func(*args, **kwargs)) return wrapper
對比一下效果。
>>> @make_bold ... def get_content(): ... """Return page content""" ... return "hello world" >>> # 不用functools.wraps的結果 >>> get_content.__name__ "wrapper" >>> get_content.__doc__ >>> # 用functools.wraps的結果 >>> get_content.__name__ "get_content" >>> get_content.__doc__ "Return page content"
???????實現裝飾器時往往不知道調用方會怎么用,所以養成好習慣加上functools.wraps吧。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/40872.html
摘要:可見裝飾器改變了函數的功能。裝飾器除了改變函數功能之外還有一個特性是,函數裝飾器在導入模塊時立即執行,而被裝飾的函數只在明確調用時運行。 什么是裝飾器 裝飾器是什么,簡單來說,裝飾器可以改變一個函數的行為,比如原本有一個函數用來計算菲波那切數列,我們給這個函數加個計算執行時間的裝飾器,這樣原來的函數不僅能夠計算菲波那切數列,而且還可以輸出計算花費了多少時間。 在Python中,有幾個很...
摘要:使用類裝飾器,優點是靈活性大,高內聚,封裝性。不過不用擔心,有,本身也是一個裝飾器,它的作用就是把原函數的元信息拷貝到裝飾器函數中,使得裝飾器函數也有和原函數一樣的元信息。 showImg(https://segmentfault.com/img/bVbrFWb?w=742&h=484);Python的裝飾器(decorator)是一個很棒的機制,也是熟練運用Python的必殺技之一。...
摘要:使用一年多了,一直知道有個裝飾器,很好用,試圖理解過,可能由于資料找的不好,自己的悟性太差,一直沒有搞清楚,今天查了一些資料,算是理解了,現在簡單記錄下。 使用python一年多了,一直知道python有個裝飾器,很好用,試圖理解過,可能由于資料找的不好,自己的悟性太差,一直沒有搞清楚,今天查了一些資料,算是理解了,現在簡單記錄下。python的裝飾器本身的功能是在不改變已有函數本身的...
摘要:裝飾器的使用符合了面向對象編程的開放封閉原則。三簡單的裝飾器基于上面的函數執行時間的需求,我們就手寫一個簡單的裝飾器進行實現。函數體就是要實現裝飾器的內容。類裝飾器的實現是調用了類里面的函數。類裝飾器的寫法比我們裝飾器函數的寫法更加簡單。 目錄 前言 一、什么是裝飾器 二、為什么要用裝飾器 ...
摘要:一般情況下,我們使用裝飾器提供的語法糖,來簡化上面的寫法像上面的情況,可以動態修改函數或類功能的函數就是裝飾器。本文標題為會打扮的裝飾器本文鏈接為參考資料修飾器的函數式編程中的裝飾器介紹思誠之道裝飾器入門與提高賴明星 裝飾器 我們知道,在 Python 中,我們可以像使用變量一樣使用函數: 函數可以被賦值給其他變量 函數可以被刪除 可以在函數里面再定義函數 函數可以作為參數傳遞給另外...
閱讀 3070·2023-04-25 16:50
閱讀 904·2021-11-25 09:43
閱讀 3512·2021-09-26 10:11
閱讀 2518·2019-08-26 13:28
閱讀 2531·2019-08-26 13:23
閱讀 2419·2019-08-26 11:53
閱讀 3566·2019-08-23 18:19
閱讀 2987·2019-08-23 16:27