zoukankan      html  css  js  c++  java
  • py10 装饰器

    装饰器

    装饰器就是闭包函数的一种应用场景

    为何要用装饰器

    开放封闭原则:对修改封闭,对扩展开放

    什么是装饰器

    装饰器他人的器具,本身可以是任意可调用对象,被装饰者也可以是任意可调用对象。

    强调装饰器的原则:1 不修改被装饰对象的源代码 2 不修改被装饰对象的调用方式

    装饰器的目标:在遵循1和2的前提下,为被装饰对象添加上新功能

    装饰器的使用

    函数不固定参数,装饰器的使用

    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若有返回值,则传给res

    有参装饰器的使用

    当装饰器带有多个参数的时候, 装饰器函数就需要多加一层嵌套,如果不调用被装饰函数,可以不多加一层(只写两层):

    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("33[32;1mUser has passed authentication33[0m")
                        res = func(*args, **kwargs)  # from home
                        print("---after authenticaion ")
                        return res
                    else:
                        exit("33[31;1mInvalid username or password33[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) ,最终让属性的显示更符合我们的直觉。

  • 相关阅读:
    关于订单创建的service层
    使用注解@RestController返回json类型的数据
    关于lombok包(可使编程便捷)的一些使用
    Django学习笔记一十三——ORM查询练习
    Django学习笔记一十二——建立多对多结构表的三种方式
    Django学习笔记一十一——ORM学习三
    Django学习笔记一十——Django项目在python脚本中的调用
    Django学习笔记〇九——路由系统
    Django学习笔记〇八——模板语言系统
    Django学习笔记〇七——MCV和MTV框架介绍
  • 原文地址:https://www.cnblogs.com/wlx97e6/p/9385082.html
Copyright © 2011-2022 走看看