python中的装饰器

Posted on 2019-03-31 15:31  Mr__Seven  阅读(121)  评论(0)    收藏  举报

 

 

装饰器是python实现AOP的一种方式,是程序开发过程中经常用到的一个功能。符合“开放封闭”原则,也就是说,已经实现的功能代码不允许修改,但可以被扩展。

首先,需要理解的是,python中所有的定义(函数、属性、类等等)都是引用,比如如下:

def test(num):
    print(num)

test(100)
print(test)
print(id(test))

运行结果如下:

分析一下,其实在定义test方法时,就是在内存开辟了一块新地址存放函数体prnt(num),而函数名test只是指向了这个内存地址。所以当python解释器执行到test(100),会找到这个函数体,将参数带进去执行。python解释器执行到print(test)的时候,也会发现test就是这个内存地址的引用,而内存地址里面就是一个函数体,所以就想这个函数体打印出来。而id(test)就是讲test引用的内存地址打印出来。

  

理解了上面以后,这里在引入一个概念——“闭包”

  闭包就是在函数内部再定义一个函数,并且这个函数用到了外部函数的变量,那么这个函数以及用到的外部函数的变量就称为闭包,可以看一下如下的代码,在函数test内部有定义了一个函数test2,而test2用到了test的num,这就是一个“闭包”。

def test(num):
    print(num)
  #闭包
def test2(): print(num + 1) return test2 res = test(100)  #100 res()  #101

运行结果:

好了,接下来有一个需求,目前有fi、f2、f3三个函数,在调用这三个函数之前进行权限验证,但是又要做到“开放封闭”的原则,既不能动f1、f2、f3原来的代码,这时候就要用到闭包:

def fun(f):
    def innerFun():
        print("=====权限验证相关=====")
        f()
    return innerFun

def f1():
    print("=====f1=====")

def f2():
    print("=====f2=====")

def f3():
    print("=====f3=====")

# 调用f1
res = fun(f1)
res()
# 调用f2
res = fun(f2)
res()
# 调用f3
res = fun(f3)
res()

运行结果:

由此可见,使用闭包,可以实现权限验证。但是这样却存在一个问题,本来调用f1只需要f1(),而现在调用f1则需要先调用fun并且将f1传入,再调用其返回。所有调用f1的地方都需要更改。这时候呢用装饰器来重新实现这个需求;

def fun(f):
    print("=====正在进行装饰=====")
    def innerFun():
        print("=====权限验证相关=====")
        f()
    return innerFun

#python解释器运行到@符号开头的这一行,就已经开始装饰了,而不是调用的时候才开始,装饰后的结果就是f1指向了fun内部的innerFun
@fun
def f1():
    print("=====f1=====")

#这时候已经装饰过了
f1()

运行结果:

简单分析一下原理:

  代码定义了两个函数,f1指向的函数块内存地址是4136587,fun指向的函数体内部又有一个函数innerFun,并且接受一个形参f。

  

当python解释器执行到@fun的时候,开始装饰。既相当于调用了fun(f1),这时候f1将自己的医用传给了fun函数的形参f既f指向了id为4136587的函数体,而f1重新指向了fun内部的innerFun的地址。

  

此时调用f1(),相当于是执行fun内部的innerFun函数,先进行权限相关验证,在执行f(),而f指向的是id为4136587的函数体的内存地址,既print("f1")。

 

这里需要注意的一点是,python解释器运行到@fun的时候就已经开始进行装饰了,而只有在调用f1()的时候才会进行验证(既运行innerFun里面的代码),从上面的结果可以看到,使用装饰器无需求该原来的代码,只需要在定义f1函数的地方加上一个“语法糖”,既@fun

 

现在我们来看看,当有多个装饰器的时候,Python解释器的执行顺序又是怎样的呢

 

def fun1(f):
    print("=====正在进行装饰fun1=====")
    def innerFun():
        print("=====权限验证相关fun1=====")
        return("==fun1=="+f()+"==fun1==")
    return innerFun

def fun2(f):
    print("=====正在进行装饰fun2=====")
    def innerFun():
        print("=====权限验证相关fun2=====")
        return("==fun2=="+f()+"==fun2==")
    return innerFun
#python解释器装饰的顺序是先装饰fun2,在装饰fun1,在装饰fun2的时候,f1就指向了fun2内部的innerFun函数,而装饰fun1的时候,f1指向了这个f1
@fun1
@fun2
def f1():
    return("===f1===")
#在调用f1的时候,由于后装饰fun1,所以此时f1指向的实际是fun1内部的innerFun函数,所以先执行fun1内部的innerFun函数
#当执行到fun1内部的innerFun函数中 return("==fun1=="+f()+"==fun1==") 时,在调用这个f()的时候又去了fun2内部的innerFun函数
#所以最终打印出来的结果是"==fun1====fun2=====f1=====fun2====fun1=="
print(f1())

运行结果:

根据之前的分析,也就很好理解为什么先装饰fun2,后装饰fun1,而先验证fun1后验证fun2了。

对于这种无参的函数,使用上面的装饰器没什么问题,但是如果是有参数的函数呢?根据分析,当装饰之后,f1指向了fun内部的innerFun,但是innerFun并不需要参数。而且需要装饰的函数可能会有很多,有些是需要参数的,有些是不需要参数的,而且参数的个数也不尽相同,这里就要用到不定长参数了。

def fun(f):
    print("=====正在进行装饰fun=====")
    #不定长参数,*args接收的是一个元组,**kwargs接收的是一个字典
    def innerFun(*args,**kwargs):
        print("=====权限验证相关fun=====")
        f(*args,**kwargs)
    return innerFun


@fun
def f1():
    print("===f1===")
@fun
def f2(num):
    print(num)
@fun
def f3(parm1,parm2):
    print(parm1+parm2)

f1()
f2(3)
f3("hello","77")

运行结果:

 现在不管函数是有参数的还是无参数的,都已经支持了,那么如果是带有返回值的函数,要怎么进行装饰呢?如果按照之前的方法,代码如下

def fun(f):
    print("=====正在进行装饰fun=====")
    #不定长参数,*args接收的是一个元组,**kwargs接收的是一个字典
    def innerFun(*args,**kwargs):
        print("=====权限验证相关fun=====")
        f(*args,**kwargs)
    return innerFun

@fun
def f2(info):
    print(info)
    return(info)

res = f2("hello world")
print("=====return value=====:%s",res)

此时,实际需要的是调用f2.并将info返回给res并打印,但是实际运行结果如下:

由此可见,实际打印出来的info是hello world 而res接收到的info却是None,为什么会这样呢?根据之前的分析,在python解释器执行到 @fun 的时候就已经开始进行装饰,装饰完以后f2不再指向原来的函数体,而是指向了fun内部的函数体,即innerFun,而innerFun并没有返回值,所以最终res接收到的就是None,那么该怎么解决这个问题呢?其实只要在innerFun内部将调用f的地方返回就可以了,代码如下:

def fun(f):
    print("=====正在进行装饰fun=====")
    #不定长参数,*args接收的是一个元组,**kwargs接收的是一个字典
    def innerFun(*args,**kwargs):
        print("=====权限验证相关fun=====")
        return f(*args,**kwargs)
    return innerFun

@fun
def f1():
    print("===f1===")
@fun
def f2(info):
    print(info)
    return(info)

res1 = f1()
res2 = f2("hello world")
print("return value : %s,%s"%(res1,res2))

运行结果:

这样来看的话,现在已经是一个通用的装饰器了,不管函数是否有参数,也不管函数是否有返回值,都可以进行装饰。接下来再说一种情况,在某些应用场景下,需要在装饰器本身需要参数,然后根据参数进行不同的操作。此时就需要在装饰器外面再封装一层,用来接收参数,代码如下:

def fun_arg(arg = "hello python"):
    def fun(f):
        print("=====正在进行装饰fun=====")
        #不定长参数,*args接收的是一个元组,**kwargs接收的是一个字典
        def innerFun(*args,**kwargs):
            print("=====权限验证相关fun=====")
            print(arg)
            return f(*args,**kwargs)
        return innerFun
    return fun

#执行步骤:首先调用fun_arg("Python")函数,最后将fun的引用返回回来,此时@fun_arg("Python")就相当于@fun,然后@fun对函数进行装饰
@fun_arg("Python")
def f1():
    print("===f1===")
@fun_arg()
def f2(info):
    print(info)
    return(info)

res1 = f1()
res2 = f2("hello world")
print("return value : %s,%s"%(res1,res2))

运行结果如下:

其实当python解释器执行到@fun_arg("Python")的时候就直接去调用了@fun_arg("Python"),而fun_arg函数最后将fun函数的引用返回回来了,这时候@fun_arg("Python")执行完以后就相当于@fun,也就是之前的装饰器,@fun再对函数进行装饰。

至此,装饰器基本用法已经全部演示完了。