函式 裝飾器

2022-06-26 02:57:09 字數 4053 閱讀 4310

一 裝飾器介紹

1.1 為何要用裝飾器

軟體的設計應該遵循開放封閉原則,即對擴充套件是開放的,而對修改是封閉的。對擴充套件開放,意味著有新的需求或變化時,可以對現有**進行擴充套件,以適應新的情況。對修改封閉,意味著物件一旦設計完成,就可以獨立完成其工作,而不要對其進行修改。

軟體包含的所有功能的源**以及呼叫方式,都應該避免修改,否則一旦改錯,則極有可能產生連鎖反應,最終導致程式崩潰,而對於上線後的軟體,新需求或者變化又層出不窮,我們必須為程式提供擴充套件的可能性,這就用到了裝飾器。

1.2 什麼是裝飾器

』裝飾』代指為被裝飾物件新增新的功能,』器』代指器具/工具,裝飾器與被裝飾的物件均可以是任意可呼叫物件。概括地講,裝飾器的作用就是在不修改被裝飾物件源**和呼叫方式的前提下為被裝飾物件新增額外的功能。裝飾器經常用於有切面需求的場景,比如:插入日誌、效能測試、事務處理、快取、許可權校驗等應用場景,裝飾器是解決這類問題的絕佳設計,有了裝飾器,就可以抽離出大量與函式功能本身無關的雷同**並繼續重用。
二 裝飾器的實現

函式裝飾器分為:無參裝飾器和有參裝飾兩種,二者的實現原理一樣,都是』函式巢狀+閉包+函式物件』的組合使用的產物。

2.1 無參裝飾器的實現

如果想為下述函式新增統計其執行時間的功能

import time

def index():

time.sleep(3)

print('welcome to the index page』)

return 200

index() #函式執行

遵循不修改被裝飾物件源**的原則,我們想到的解決方法可能是這樣

start_time=time.time()

index() #函式執行

stop_time=time.time()

print('run time is %s' %(stop_time-start_time))

考慮到還有可能要統計其他函式的執行時間,於是我們將其做成乙個單獨的工具,函式體需要外部傳入被裝飾的函式從而進行呼叫,我們可以使用引數的形式傳入

start_time=time.time()

res=func()

stop_time=time.time()

print('run time is %s' %(stop_time-start_time))

return res

但之後函式的呼叫方式都需要統一改成

這便違反了不能修改被裝飾物件呼叫方式的原則,於是我們換一種為函式體傳值的方式,即將值包給函式,如下

def timer(func):

start_time=time.time()

res=func()

stop_time=time.time()

print('run time is %s' %(stop_time-start_time))

return res

至此我們便實現了乙個無參裝飾器timer,可以在不修改被裝飾物件index源**和呼叫方式的前提下為其加上新功能。但我們忽略了若被裝飾的函式是乙個有參函式,便會丟擲異常

def home(name):

time.sleep(5)

print('welcome to the home page',name)

home=timer(home)

home('egon')

#丟擲異常

def timer(func):

start_time=time.time()

res=func(*args,**kwargs)

stop_time=time.time()

print('run time is %s' %(stop_time-start_time))

return res

此時我們就可以用timer來裝飾帶引數或不帶引數的函式了,但是為了簡潔而優雅地使用裝飾器,python提供了專門的裝飾器語法來取代index=timer(index)的形式,需要在被裝飾物件的正上方單獨一行新增@timer,當直譯器解釋到@timer時就會呼叫timer函式,且把它正下方的函式名當做實參傳入,然後將返回的結果重新賦值給原函式名

@timer # index=timer(index)

def index():

time.sleep(3)

print('welcome to the index page')

return 200

@timer # index=timer(home)
 def home(name):

time.sleep(5)

print('welcome to the home page』,name)

如果我們有多個裝飾器,可以疊加多個

@deco3

@deco2

@deco1

def index():

pass

疊加多個裝飾器也無特殊之處,上述**語義如下:

index=deco3(deco2(deco1(index)))

2.2 有參裝飾器的實現

了解無參裝飾器的實現原理後,我們可以再實現乙個用來為被裝飾物件新增認證功能的裝飾器,實現的基本形式如下

def deco(func):

編寫基於檔案的認證,認證通過則執行res=func(*args,**kwargs),並返回res

def deco(func):

if driver == 'file':

編寫基於檔案的認證,認證通過則執行res=func(*args,**kwargs),並返回res

elif driver == 'mysql':

編寫基於mysql認證,認證通過則執行res=func(*args,**kwargs),並返回res

def auth(driver):

def deco(func):

……return deco

此時我們就實現了乙個有參裝飾器,使用方式如下

先呼叫auth_type(driver='file'),得到@deco,deco是乙個閉包函式,

包含了對外部作用域名字driver的引用,@deco的語法意義與無參裝飾器一樣

@auth(driver='file')

def index():

pass

@auth(driver='mysql')

def home():

pass

可以使用help(函式名)來檢視函式的文件注釋,本質就是檢視函式的doc屬性,但對於被裝飾之後的函式,檢視文件注釋

@timer

def home(name):

'''home page function

:param name: str

:return: none

'''time.sleep(5)

print('welcome to the home page',name)

print(help(home))

'''列印結果:

none

def timer(func):

start_time=time.time()

res=func(*args,**kwargs)

stop_time=time.time()

print('run time is %s' %(stop_time-start_time))

return res

按照上述方式來實現保留原函式屬性過於麻煩,functools模組下提供乙個裝飾器wraps專門用來幫我們實現這件事,用法如下

from functools import wraps

def timer(func):

@wraps(func)

start_time=time.time()

res=func(*args,**kwargs)

stop_time=time.time()

print('run time is %s' %(stop_time-start_time))

return res

函式裝飾器 類裝飾器

一 函式裝飾函式 defwrapfun func definner a,b print function name func.name r func a,b return r return inner wrapfun defmyadd a,b return a b print myadd 2,3 二...

python裝飾器 函式裝飾器,類裝飾器

只要實現此 模式,這個obj就叫乙個裝飾器 參考 函式裝飾器 例子 def decorator func def inner args,kwargs print before.res func args,kwargs print after.return res return inner decor...

python 裝飾器 函式裝飾器 類裝飾器

python函式裝飾器和類裝飾器筆記.usr bin env python coding utf 8 author ivan file decorators.py version from functools import wraps 裝飾器 目的是為了給函式新增附加功能 1.不帶引數裝飾器 此方式...