裝飾器就是閉包函數的一種應用場景javascript
開放封閉原則:對修改封閉,對擴展開放html
什麼是裝飾器java
裝飾器他人的器具,自己能夠是任意可調用對象,被裝飾者也能夠是任意可調用對象。python
強調裝飾器的原則:1 不修改被裝飾對象的源代碼 2 不修改被裝飾對象的調用方式git
裝飾器的目標:在遵循1和2的前提下,爲被裝飾對象添加上新功能github
裝飾器的使用flask
函數不固定參數,裝飾器的使用session
import time
def timmer(func):
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
return wrapper
@timmer # 至關於 foo = timmer(foo)
def foo():
time.sleep(2)
print('from foo')
@timmer # 至關於 foo = timmer(foo)
def foo1(name):
time.sleep(2)
print('from foo', name)
foo()
foo1('weilianxin')
上述的foo通過裝飾器裝飾後,foo已經至關於wrapper,foo1亦是如此,因此運行foo和foo1至關於運行wrapper,傳參也是向wrapper傳參。閉包
foo1給timmer(func):,name給了wrapper(*args,**kwargs):,而後傳給res=func(*args,**kwargs),原foo1如有返回值,則傳給resapp
當裝飾器帶有多個參數的時候, 裝飾器函數就須要多加一層嵌套,若是不調用被裝飾函數,能夠很少加一層(只寫兩層):
def auth(auth_type): print("auth func:", auth_type) def outer_wrapper(func): print('123456') def wrapper(*args, **kwargs): print("wrapper func args:", *args, **kwargs) if auth_type == "local": username = input("Username:").strip() password = input("Password:").strip() if user == username and passwd == password: print("\033[32;1mUser has passed authentication\033[0m") res = func(*args, **kwargs) # from home print("---after authenticaion ") return res else: exit("\033[31;1mInvalid username or password\033[0m") elif auth_type == "ldap": print("搞毛線ldap,不會。。。。") return wrapper return outer_wrapper @auth(auth_type="local") # home = wrapper() def home(): print("welcome to home page") return "from home" @auth(auth_type="ldap") def bbs(): print("welcome to bbs page") print('===============',home()) # wrapper() # bbs()
至關於xx = auth("local") home = xx(home)--簡單說就是先執行最外層函數,而後剩下和以前同樣
正常一層爲@timer------(@函數名),兩層的時候auth(auth_type="local")返回內層wrapper函數名,也就是至關於@wrapper
auth_type="local"會傳參給auth(auth_type),home傳給outer_wrapper(func),home如有參數則相似上一種,繼續往裏傳。
又如flask源碼中的:
def route(self, rule, **options): """Like :meth:`Flask.route` but for a blueprint. The endpoint for the :func:`url_for` function is prefixed with the name of the blueprint. """ def decorator(f): endpoint = options.pop("endpoint", f.__name__) self.add_url_rule(rule, endpoint, f, **options) return f return decorator
flask的藍圖route源碼中的裝飾器, 最內層直接返回return f 並無多加一層處理的函數, 在無需對被裝飾函數進行過多處理的時候這是較爲方便的作法. route源碼中只是對裝飾器參數進行了處理.
注意:
裝飾器中函數上面的@fun在解釋器走到函數定義時會運行代碼,運行外層函數:
import time def timer(func): print('adfadsfasf') def deco(*args, **kwargs): start = time.time() func(*args, **kwargs) end = time.time() print("time is", end-start) return deco @timer # test1 = timer(test1) def test1(): time.sleep(3) print("it is test1") @timer def test2(): time.sleep(3) print("it is test2") @timer def test3(name): time.sleep(1) print("it is test3", name) # test1 = timer(test1) # test1() # test2() # test3("weilianxin") pass
在調用語句被註釋後,依然執行了@timmer,執行了外層函數,輸出了三行
adfadsfasf
adfadsfasf
adfadsfasf
多個裝飾器的原則:(極其重要)
裝飾器的加載順序是:由下而上,裝飾器的執行順序是:由上而下。
執行從上至下,按順序執行時,遇到fun(被裝飾函數的調用)纔會跳轉到另外一個裝飾器繼續執行(由於能夠別的裝飾器函數裏也有fun),可是遇到第一個return就直接返回了,無論別的裝飾器有沒有執行成。
要點說明:
def bold(fun): print('----a----') def inner1(): print('----1----') fun() print('----1111----') return "11111111111111" return inner1 def italic(fun): print('----b----') def inner2(): print('----2----') fun() print('----2222----') return "2222222222222" return inner2 @bold @italic def test(): print("123456") return 'hello python decorator' ret = test() print(ret)
裝飾器的加載順序是:由下而上,裝飾器的執行順序是:由上而下,遇到第一個fun()跳轉到下一個裝飾器,遇到第一個return返回,結果以下:
----b---- ----a---- ----1---- ----2---- # 前四行是順序 123456 # fun()執行 ----2222---- ----1111---- 11111111111111 # 第一個return
上面的例子是簡單的說明要點的重要性
要點說明2:
def bold(fun): print('----a----') def inner1(): print('----1----') # fun() # print('----1111----') return fun() # return fun()至關於fun() 而後return None,由於不寫return和return None效果同樣,因此這句話就至關於執行fun,return能夠忽略 return inner1 def italic(fun): print('----b----') def inner2(): print('----2----') # fun() # print('----2222----') return "222222222222" return inner2 def line(fun): print('----c----') def inner3(): print('----3----') # fun() # print('----3333----') return fun() return inner3 @bold @italic @line def test(): print("123456") return 'hello python decorator' ret = test() print(ret)
在第一個裝飾器中遇到fun()跳轉到第二個裝飾器執行,遇到return返回,下面的都不執行,結果以下:
----c---- ----b---- ----a---- ----1---- ----2---- 222222222222
注意,return fun()至關於fun(),能夠理解成先執行fun() 而後return None,由於不寫return和return None效果同樣,因此這句話就至關於執行fun,return能夠忽略
要點說明三:
def bold(fun): print('----a----') def inner1(): print('----1----') return '1111111111111' return inner1 def italic(fun): print('----b----') def inner2(): print('----2----') return fun() # return fun()至關於fun() 而後return None,由於不寫return和return None效果同樣,因此這句話就至關於執行fun,return能夠忽略 return inner2 def line(fun): print('----c----') def inner3(): print('----3----') return inner3 @bold @italic @line def test(): print("123456") return 'hello python decorator' ret = test() print(ret)
這裏遇到第一個return就已經返回,下面的fun都不執行,結果以下:
----c---- ----b---- ----a---- ----1---- 1111111111111
若是你理解了,能夠看看flask的登錄驗證裝飾器放置的位置在上好仍是在下好
flask登錄驗證裝飾器和路由裝飾器:
from flask import Flask, render_template, request, redirect, session, url_for app = Flask(__name__) app.debug = True app.config['SECRET_KEY'] = '123456' app.config.from_object("settings.DevelopmentConfig") USERS = { 1: {'name': '張桂坤', 'age': 18, 'gender': '男', 'text': "當眼淚掉下來的時候,是真的累了, 其實人生就是這樣: 越不過的無奈,聽不完的謊話,看不透的人心放不下的牽掛,經歷不完的酸甜苦辣,這就是人生,這就是生活。"}, 2: {'name': '主城', 'age': 28, 'gender': '男', 'text': "高中的時候有一個同窗家裏窮,每頓飯都是膜膜加點水,有時候吃點鹹菜,咱們六科老師天天下課都叫他去辦公室回答問題背誦課文,而後說太晚啦一塊兒吃個飯,後來他考上了人大,拿到通知書的時候給每一個老師磕了一個頭"}, 3: {'name': '服城', 'age': 18, 'gender': '女', 'text': "高中的時候有一個同窗家裏窮,每頓飯都是膜膜加點水,有時候吃點鹹菜,咱們六科老師天天下課都叫他去辦公室回答問題背誦課文,而後說太晚啦一塊兒吃個飯,後來他考上了人大,拿到通知書的時候給每一個老師磕了一個頭"}, } def wapper(func): def inner(*args, **kwargs): user = session.get('user_info') if not user: return redirect("/login") return func(*args, **kwargs) return inner @app.route('/detail/<int:nid>', methods=['GET'], endpoint='l0') # 配置動態url @wapper def detail(nid): user = session.get('user_info') if not user: return redirect('/login') info = USERS.get(nid) # 獲取動態url return render_template('detail.html', info=info) @app.route('/index', methods=['GET']) def index(): user = session.get('user_info') if not user: # return redirect('/login') url = url_for('l1') # url_for能夠進行反向解析 return redirect(url) return render_template('index.html', user_dict=USERS) @app.route('/login', methods=['GET', 'POST'], endpoint='l1') # endpoint設置url別名 def login(): if request.method == "GET": return render_template('login.html') else: # request.query_string user = request.form.get('user') pwd = request.form.get('pwd') if user == 'alex' and pwd == '123': session['user_info'] = user # 設置session # return redirect('http://www.luffycity.com') return redirect('/index') return render_template('login.html', error='用戶名或密碼錯誤') if __name__ == '__main__': app.run() """ 對象後面加括號調用對象的call方法, run(): from werkzeug.serving import run_simple run_simple(host, port, self, **options) run_simple()的第三個參數是self,是上面實例化的app,因此對象()調用的是對象的call方法 def __call__(self, environ, start_response): # environ,是請求相關,start_response是響應相關 return self.wsgi_app(environ, start_response) """ '''路由: def route(self, rule, **options): """Like :meth:`Flask.route` but for a blueprint. The endpoint for the :func:`url_for` function is prefixed with the name of the blueprint. """ def decorator(f): endpoint = options.pop("endpoint", f.__name__) self.add_url_rule(rule, endpoint, f, **options) # 路由最關鍵的就是執行這句話 return f return decorator '''
這裏的wrapper裝飾器只能放在route裝飾器下面,由於wrapper裝飾器在if not user時,會直接return,這樣,後面的路由就沒法添加了,suoyi只能在下面,在下面也有問題,由於wrapper裝飾後,函數名變成了inner,這樣不少函數名都變成inner,endpoint會重複,因此要指定一下,或者使用裝飾器修復。
介紹如何使用Python的裝飾器裝飾一個類的方法,同時在裝飾器函數中調用類裏面的其餘方法。以捕獲一個方法的異常爲例來進行說明。
def catch_exception(origin_func): def wrapper(self, *args, **kwargs): try: u = origin_func(self, *args, **kwargs) return u except Exception: self.revive() #不用顧慮,直接調用原來的類的方法 return 'an Exception raised.' return wrapper class Test(object): def __init__(self): pass def revive(self): print('revive from exception.') # do something to restore @catch_exception def read_value(self): print('here I will do something.') # do something. test = Test() test.read_value()
注意裝飾器是寫在類的定義外面的
functools.wraps的做用:
咱們在使用 Decorator 的過程當中,不免會損失一些本來的功能信息(.__name__等)。直接拿 stackoverflow 裏面的栗子
而functools.wraps 則能夠將原函數對象的指定屬性複製給包裝函數對象, 默認有 __module__、__name__、__doc__,或者經過參數選擇。代碼以下:
from functools import wraps def logged(func): @wraps(func) def with_logging(*args, **kwargs): print func.__name__ + " was called" return func(*args, **kwargs) return with_logging @logged def f(x): """does some math""" return x + x * x print f.__name__ # prints 'f' print f.__doc__ # prints 'does some math'
預備知識
在瞭解wraps
修飾器以前,咱們首先要了解partial
和update_wrapper
這兩個函數,由於在wraps
的代碼中,用到了這兩個函數。
partial
首先說partial
函數,在官方文檔的描述中,這個函數的聲明以下:functools.partial(func, *args, **keywords)
。它的做用就是返回一個partial
對象,當這個partial
對象被調用的時候,就像經過func(*args, **kwargs)
的形式來調用func
函數同樣。若是有額外的 位置參數(args) 或者 關鍵字參數(*kwargs) 被傳給了這個partial
對象,那它們也都會被傳遞給func
函數,若是一個參數被屢次傳入,那麼後面的值會覆蓋前面的值。
我的感受這個函數很像C++中的bind
函數,都是把某個函數的某個參數固定,從而構造出一個新的函數來。好比下面這個例子:
from functools import partial def add(x:int, y:int): return x+y # 這裏創造了一個新的函數add2,只接受一個整型參數,而後將這個參數統一加上2 add2 = partial(add, y=2) add2(3) # 這裏將會輸出5
這個函數是使用C而不是Python實現的,可是官方文檔中給出了Python實現的代碼,以下所示,你們能夠進行參考:
def partial(func, *args, **keywords): def newfunc(*fargs, **fkeywords): newkeywords = keywords.copy() newkeywords.update(fkeywords) return func(*args, *fargs, **newkeywords) newfunc.func = func newfunc.args = args newfunc.keywords = keywords return newfunc
接下來,咱們再來聊一聊update_wrapper
這個函數,顧名思義,這個函數就是用來更新修飾器函數的,具體更新些什麼呢,咱們能夠直接把它的源碼搬過來看一下:
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__') WRAPPER_UPDATES = ('__dict__',) def update_wrapper(wrapper, wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES): for attr in assigned: try: value = getattr(wrapped, attr) except AttributeError: pass else: setattr(wrapper, attr, value) for attr in updated: getattr(wrapper, attr).update(getattr(wrapped, attr, {})) wrapper.__wrapped__ = wrapped return wrapper
你們能夠發現,這個函數的做用就是從 被修飾的函數(wrapped) 中取出一些屬性值來,賦值給 修飾器函數(wrapper) 。爲何要這麼作呢,咱們看下面這個例子。
首先咱們寫個自定義的修飾器,沒有任何的功能,僅有文檔字符串,以下所示:
def wrapper(f): def wrapper_function(*args, **kwargs): """這個是修飾函數""" return f(*args, **kwargs) return wrapper_function @wrapper def wrapped(): """這個是被修飾的函數""" print('wrapped') print(wrapped.__doc__) # 輸出`這個是修飾函數` print(wrapped.__name__) # 輸出`wrapper_function`
從上面的例子咱們能夠看到,我想要獲取wrapped
這個被修飾函數的文檔字符串,可是卻獲取成了wrapper_function
的文檔字符串,wrapped
函數的名字也變成了wrapper_function
函數的名字。這是由於給wrapped
添加上@wrapper
修飾器至關於執行了一句wrapped = wrapper(wrapped)
,執行完這條語句以後,wrapped
函數就變成了wrapper_function
函數。遇到這種狀況該怎麼辦呢,首先咱們能夠手動地在wrapper
函數中更改wrapper_function
的__doc__
和__name__
屬性,但聰明的你確定也想到了,咱們能夠直接用update_wrapper
函數來實現這個功能。
咱們對上面定義的修飾器稍做修改,添加了一句update_wrapper(wrapper_function, f)
。
from functools import update_wrapper def wrapper(f): def wrapper_function(*args, **kwargs): """這個是修飾函數""" return f(*args, **kwargs) update_wrapper(wrapper_function, f) # << 添加了這條語句 return wrapper_function @wrapper def wrapped(): """這個是被修飾的函數""" print('wrapped') print(wrapped.__doc__) # 輸出`這個是被修飾的函數` print(wrapped.__name__) # 輸出`wrapped`
此時咱們能夠發現,__doc__
和__name__
屬性已經可以按咱們預想的那樣顯示了,除此以外,update_wrapper
函數也對__module__
和__dict__
等屬性進行了更改和更新。
OK,至此,咱們已經瞭解了partial
和update_wrapper
這兩個函數的功能,接下來咱們翻出wraps
修飾器的源碼:
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__') WRAPPER_UPDATES = ('__dict__',) def wraps(wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES): return partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)
沒錯,就是這麼的簡單,只有這麼一句,咱們能夠看出,wraps
函數其實就是一個修飾器版的update_wrapper
函數,它的功能和update_wrapper
是如出一轍的。咱們能夠修改咱們上面的自定義修飾器的例子,作出一個更方便閱讀的版本。
from functools import wraps def wrapper(f): @wraps(f) def wrapper_function(*args, **kwargs): """這個是修飾函數""" return f(*args, **kwargs) return wrapper_function @wrapper def wrapped(): """這個是被修飾的函數 """ print('wrapped') print(wrapped.__doc__) # 輸出`這個是被修飾的函數` print(wrapped.__name__) # 輸出`wrapped`
至此,我想你們應該明白wraps
這個修飾器的做用了吧,就是將 被修飾的函數(wrapped) 的一些屬性值賦值給 修飾器函數(wrapper) ,最終讓屬性的顯示更符合咱們的直覺。