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) ,最终让属性的显示更符合咱们的直觉。

相关文章
相关标签/搜索