重点面试知识——多个装饰器叠加的原理

当一个被装饰的对象同时叠加多个装饰器时

装饰器的加载顺序是:由下而上

装饰器的执行顺序是:由上而下

加载装饰器就是将原函数名与装饰器内部的wrapper函数进行偷梁换柱

执行装饰器实际上就是执行装饰器内部的wrapper函数。

我们来看下面这段代码

我们定义了两个装饰器:无参装饰器timmer与有参装饰器auth

我们用这两个装饰器去修饰index函数。让我们看看到底是发生了一件什么事

import time


def timmer(func):      
    def wrapper1(*args, **kwargs):
        print('===================================>wrapper1运行了')
        start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print('run time is %s' % (stop - start))
        return res

    return wrapper1


def auth(engine='file'):
    def xxx(func):
        def wrapper2(*args, **kwargs):
            print('===================================>wrapper2运行了')
            name = input('username>>>: ').strip()
            pwd = input('password>>>: ').strip()
            if engine == 'file':
                print('基于文件的认证')
                if name == 'egon' and pwd == '123':
                    print('login successfull')
                    res = func(*args, **kwargs)
                    return res
            elif engine == 'mysql':
                print('基于mysql的认证')
            elif engine == 'ldap':
                print('基于ldap的认证')
            else:
                print('错误的认证源')

        return wrapper2

    return xxx


@timmer
@auth(engine='file')
def index():
    print('welcome to index page')
    time.sleep(2)


index()

文章一开头,我们就已经说了,装饰器的加载顺序是由下而上的。就是说离被装饰函数最近的装饰器先加载。

那么在这段代码中可以看到,是@auth(engine='file')先被加载,在加载的时候,python一遇到函数名+()就会触发函数的执行,也就是说,加载的第一步就是执行了auth(engine='file')函数。

我们拐过头来去看auth函数的代码,执行auth函数其实就干了两件事,定义一个叫xxx的函数,然后将xxx函数内存地址作为返回值返回。没有调用xxx就不会触发xxx函数的执行。

auth(engine='file')函数执行完毕以后,语法糖:'@'符号开始生效,它的作用就是将正下方的函数名(函数内存地址)作为参数传入装饰器并将原函数名作为变量名去接收。

在这里就是干了这样一件事:index=xxx(index)(这里需要注意。第一:为什么是xxx呢?因为在auth函数执行完毕后把xxx函数的内存地址作为返回值返回了。所以这里是xxx。第二:括号内的index和等号左边的index不是同一个东西,括号内的index是原函数index的内存地址,等号左边的index就是一个变量名。)然后其实现在代码就是变成了下述代码的样子,只是这是在后台自动做的,我们看不到。这些都是Python解释器默默地帮我们实现的。

@timmer
index = xxx(index)    # index = wrapper2函数内存地址
def index():
    print('welcome to index page')
    time.sleep(2)

我们看现在的代码,xxx函数加(),一看到函数名+()就会执行函数,返回上面代码去看,执行xxx函数干了两件事,干了两件事,定义了wrapper2函数,然后将wrapper2函数内存地址作为返回值返回。所以,变量名index是指向wrapper2函数的内存地址。

然后再向上加载timmer装饰器。函数名没有加(),则执行@语法。@的作用咱们上面说过了,忘掉的可以再看看。index这个变量名,指向的是wrapper2函数,将index函数名传入timmer函数,还是做了两件事,定义wrapper1函数,将wrapper1函数的内存地址作为返回值返回。这个时候已经变成了这个样子

index = wrapper1(index) 
index = xxx(index)    # index = wrapper2函数内存地址
def index():
    print('welcome to index page')
    time.sleep(2)

这个时候的第一行中,括号内的index指向的是wrapper2函数的内存地址,等号左边的index是一个新的变量名。这时的index指向的是wrapper1函数的内存地址。

以上就是装饰器加载时python解释器在背后默默帮我们做的事。

执行的时候会按照顺序依次执行,先执行wrapper1函数,再执行wrapper2函数,再执行被装饰函数。

这就是我们上面所说的:

装饰器的加载顺序是:由下而上

装饰器的执行顺序是:由上而下

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章