python装饰器--你绝对能掌握!
发布日期:2021-09-14 03:23:57 浏览次数:8 分类:技术文章

本文共 6909 字,大约阅读时间需要 23 分钟。

装饰器

什么是装饰器?它有什么作用?我们先不去纠结这些,先从一个需求来入手。

需求:

在一个公司里面,有好几个部门A、B、C、D,B、C、D三个部门都会使用到A部门编写的一系列函数,但是他们并不参与函数内部的修改与编写,只能调用这些函数,获得函数返回的结果或者使用函数实现的功能。那么问题来了,老板发话说,不能让什么人都可以随便调用我们的函数,比如新人未转正的。那就需要让每个人在调用函数之前,进行身份验证,如果权限不足的人员是无法调用这些接口的,这样就避免了没有经验的人员胡乱调用接口,使、接口出现不可预知的错误,影响公司运营。

那么该怎么实施呢?老板找了几个员工,让他们每个人都提供一套解决方案。下面我们就来看看这几位员工提供了什么方法:
员工1:
员工1让跑到各个部门分别告诉每个员工,让他们没有转正的员工不能调用这些接口,只能有经验的员工才能调用。
员工2:
员工2是没有采取员工1的方法,而是准备在函数内部动脑筋:

def f1():    #验证1    print('f1')def f2():    #验证1    print('f2')def f3():    #验证1    print('f3')

员工3:

员工3是这样做的:

def checkLogin():    #验证1def f1():    checkLogin()    print('f1')def f2():    checkLogin()    print('f2')def f3():    checkLogin()    print('f3')

现在我们应该能看的出来哪个员工的方法有优势了吧。员工1用口头的话去限制别人,那肯定是不可行的,员工2虽然加了验证,但是万一接口数量巨多,那无疑又增加了很多代码。而员工3在员工2的基础上单独定义了一个验证函数,然后在每一个函数内部调用这个验证函数,这个方法已经实现的很好了!但是!还是不行,因为你要深入到每一个函数的内部去调用这个验证函数,无形中修改了我们既有的函数,这不符合开放封闭原则。什么是开放封闭原则呢?通俗点说就是:不能修改原有功能的代码,但是可以拓展!因此员工3的方案也没能得到老板的认可!

那么到底要怎么样在不改变原有功能代码的情况下,去拓展功能呢?接下来我们就引入装饰器,先来看看一下代码:

def w1(func):    def inner():        #验证代码        func()    return inner@w1def f1():    print('f1')@w1def f2():    print('f2')@w1def f3():    print('f3')

那这段代码是什么意思呢?函数w1看得懂吧,这是闭包!不懂的可以去看看我的另一篇介绍闭包的博客:

以上代码执行顺序:
@w1
def f4():
先执行w1函数,在执行f4函数.
先看看装饰器的效果,我们不需要深入函数内部修改代码,也不需要使用大量重复代码,只是定义了一个闭包函数,然后在每一个要调用函数的顶部加上@闭包函数,就可以实现功能,而且还满足开放封闭原则。
那么什么是装饰器呢?我们先来分析一下装饰器的原理:

装饰器的原理

红框其实就是装饰器的原理.

我们将test1给set_func函数传递过去. 然后调用ret().
执行逻辑:
1.执行set_func() , 将test1传递给func参数.
2.返回内部函数call_func , 给ret
3.调用ret() , 相当于调用了 call_func
4.执行权限验证
5.调用func() , 相当于调用了test1
但是我们这里调用的是ret(),难道要把每一个调用test1的地方都改成ret吗?那不就又变得很麻烦了吗?接下来看下面的程序:

ret可以改名为test1. 然后调用test1即可.

调用test1(),其实就是调用了 call_func. 执行权限验证之后,再执行原来的test1.
注意当将test1作为参数传递过去的时候, 这个参数就指向了原来的test1.
所以后来给test1覆盖变为 set_func的返回值是完全没有问题的.

为什么要给ret 改为test1?

这样做的话, 原来函数调用都不用修改,但是执行的过程其实已经变了, 该加的验证也加 了.

这就是装饰器. 不修改原来代码,但修改逻辑.
下面来小结一下:
上面一个程序中,程序从上到下执行,首先定义了一个函数set_func,然后里面嵌套了一个函数call_func,而函数set_func的参数被内嵌函数call_func使用,而且函数set_func返回内嵌函数的引用,很明显这是一个闭包函数。
接着下面,定义了一个test1函数,代表了我们已经实现好的而且不能更改内部功能的函数。再向下,调用set_func函数而且把函数的引用test1当做参数传递进去,即此时func=test1,然后set_func函数返回call_func的引用,那么此时,test1=call_func,但是之前当做参数传入的test1保持不变,它还是指向test1函数。接下来执行test1(),因为此时 test1=call_func,那其实就是调用函数call_func并执行,装饰器开始对test1进行装饰,首先打印出:—这是权限验证1—-和—这是权限验证2—,接下来就是调用并执行fun函数,在此之前func=test1,那么在此处执行func(),就是在执行test1(),于是去调用test1函数,打印出—–test1—-。这就实现了先执行权限验证程序,只有验证程序通过了,再去调用接口函数,这就是装饰器的原理。

装饰器的理解

装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限验证等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以腠理出大量与函数本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

讲完了原理与概念,我们就回到正规的装饰器写法上来,刚才只是通过例子来介绍装饰器的原理,那么真正的装饰器程序格式应该如下:

def w1(fun):    print('...装饰器开始装饰...')    def inner():         print('...验证权限...')         fun()    return inner@w1def test():    print('test')test()

执行结果如下:

...装饰器开始装饰......验证权限...test

其实当python解释器读取到@w1时,就开始对test函数开始修饰了,此时test已经不是指向原来的test()了,而是指向闭包函数外函数返回的内函数的引用,指向test函数的test已经被被传入到闭包函数中了,此时fun=test,即变量fun保存的是test函数的引用。

对有参数的函数进行装饰

以上讲解的都是对无参数的函数的装饰,那么装饰器对有参数的函数是怎么装饰的呢?请看下面的代码段:

def set_func(func):    def call_func(num):      #这里要使用形参接收实参        print('这是权限验证代码')        func(num)   #这里要使用形参接收实参    return call_func@set_funcdef test1(num):    print("这是功能函数————%s" % num)test1(100)

逻辑分析:

程序执行到@set_func 的时候,开始对test1进行装饰,即此时test1=set_func(test1),而set_func的返回值为call_func,即此时test1=call_func,而func指向原始功能函数test1,执行test1(100)等同于执行call_func(100),然后执行装饰代码对原始功能函数test1进行装饰,然后执行func(100),等同于执行原始功能函数test1(100),则开始执行功能函数,此时参数num也传进去了。

对不定长参数的函数进行装饰

def w_add(func):    print("开始装饰")    def inner(*args, **kwargs):        print('add inner called')        func(*args, **kwargs)    return inner@w_adddef add(a, b,**kwargs):    print('%d + %d = %d' % (a, b, a + b))    print(kwargs)@w_adddef add2(a, b, c,**kwargs):    print('%d + %d + %d = %d' % (a, b, c, a + b + c))    print(kwargs)add(2, 4,m=100)add2(2, 4, 6,m=200)

对带返回值的函数进行装饰

def set_func(func):    def call_func():        print('set_func inner called start')        str = func()        print('set_func inner called end')        return str    return call_func@set_funcdef test():    print('this is test fun')    return 'hello world'ret = test()print('ret value is %s' % ret)

输出结果为:

set_func inner called startthis is test funset_func inner called endret value is hello world

两个装饰器装饰一个函数

def set_func(fun1):    print('----a----')    def call_func1():        print('----1----')        return '' + fun() + ''    return call_func1def set_func(fun2):    print('----b----')    def call_func2():        print('----2----')        return '' + fun() + ''    return call_func2@set_func1@set_func2def test():    print('----c----')    print('----3----')    return 'life is short,you need python'ret = test()print(ret)

输出结果为:

----b--------a--------1--------2--------c--------3----hello python decorator

程序分析:

程序从上到下执行,执行到def的时候,都是在定义一个函数,并不去执行。当执行到@set_func1的时候,准备进行装饰,于是向下寻找一个函数,但是发现下面并不是一个函数,而是另外一个装饰器,于是set_func1暂停装饰,先让set_func2进行装饰,那么此时新test=set_func2(原test),即原函数test的引用被当做参数传入进set_func2中,使得 新test=call_func2,而call_func2中的fun就指向了原test函数。在这里不要晕,弄清楚哪个是原test函数,哪个是新test函数。此时就会打印出b,set_func2装饰完成后返回到@set_func1在去执行@set_func2的装饰,那么此时就会将 新test(即新test=call_func2)当做参数传入到set_func1中,而此时 新新test=set_func1(新test),那么也就有 新新test=call_func1,而call_func1中的fun=新test。
那么当程序执行到ret=test()时,此时的test=新新test,指向set_func1中的call_func1,则会打印出1,然后执行:

return '' + fun() + ''

那么根据以上分析可以知道,set_func1中的fun函数执行新test函数(set_func2中的call_func2函数),于是去调用call_func2,然后打印出2,然后执行:

return '' + fun() + ''

又以为set_func1中的fun指向原test函数,然后就去调用原test函数,打印出c和3,接着return ‘life is short,you need python’字符串,返回到call_func2中,组合成

'' + 'life is short,you need python' + ''

接着继续向上返回,返回到set_func1中,组合成:

'' + '' + 'life is short,you need python' + '' + ''

因此最后打印出:

hello python decorator

所以最后的结果为:

----b--------a--------1--------2--------c--------3----hello python decorator

附上一张流程图:

带参数的装饰器

def func_args(pre='xiaoqiang'):    def w_test_log(func):        def inner():            print('...记录日志...visitor is %s' % pre)            func()        return inner    return w_test_log# 带有参数的装饰器能够起到在运行时,有不同的功能# 先执行func_args('wangcai'),返回w_test_log函数的引用# @w_test_log# 使用@w_test_log对test_log进行装饰@func_args('wangcai')def test_log():    print('this is test log')test_log()

输出结果为:

...记录日志...visitor is wangcaithis is test log

通用装饰器

其实,不管是装饰带参数的函数还是不带参数的函数,我们都可以使用一个通用的装饰器来装饰他们,如下:

def set_func(func):    def call_func(*args, **kwargs):        ret=func(*args, **kwargs)        return ret    return call_func

即参数全部使用*args和**kwargs来传递,可以传递参数也可以不传递,都不会报错。

类装饰器

class Test(object):    def __init__(self, func):        print('test init')        print('func name is %s ' % func.__name__)        self.__func = func    def __call__(self, *args, **kwargs):        print('装饰器中的功能')        self.__func()@Testdef test():    print('this is test func')test()

输出结果:

test initfunc name is test 装饰器中的功能this is test func

程序分析:

当python解释器执行到到@Test时,会把当前test函数作为参数传入Test对象,调用init方法,同时将test函数指向创建的Test对象,那么在接下来执行test()的时候,其实就是直接对创建的对象进行调用,执行其call方法。

转载地址:https://blog.csdn.net/weixin_40612082/article/details/80569784 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:python中的装包与拆包
下一篇:闭包的概念与作用

发表评论

最新留言

不错!
[***.144.177.141]2024年04月03日 09时50分48秒