py10 裝飾器

裝飾器

裝飾器就是閉包函數的一種應用場景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

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'

functools.wraps原理解析

預備知識

在瞭解wraps修飾器以前,咱們首先要了解partialupdate_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

接下來,咱們再來聊一聊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) 。爲何要這麼作呢,咱們看下面這個例子。

自定義修飾器v1

首先咱們寫個自定義的修飾器,沒有任何的功能,僅有文檔字符串,以下所示:

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函數來實現這個功能。

自定義修飾器v2

咱們對上面定義的修飾器稍做修改,添加了一句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__等屬性進行了更改和更新。

wraps修飾器

OK,至此,咱們已經瞭解了partialupdate_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是如出一轍的。咱們能夠修改咱們上面的自定義修飾器的例子,作出一個更方便閱讀的版本。

自定義修飾器v3

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) ,最終讓屬性的顯示更符合咱們的直覺。

相關文章
相關標籤/搜索