python装饰器--你绝对能掌握!

装饰器

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

需求:

在一个公司里面,有好几个部门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():
    #验证1
def 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

@w1
def f1():
    print('f1')

@w1
def f2():
    print('f2')

@w1
def f3():
    print('f3')

那这段代码是什么意思呢?函数w1看得懂吧,这是闭包!不懂的可以去看看我的另一篇介绍闭包的博客:https://blog.csdn.net/weixin_40612082/article/details/80550557
以上代码执行顺序:
@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

@w1
def 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_func
def 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_add
def add(a, b,**kwargs):
    print('%d + %d = %d' % (a, b, a + b))
    print(kwargs)


@w_add
def 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_func
def test():
    print('this is test fun')
    return 'hello world'

ret = test()
print('ret value is %s' % ret)

输出结果为:

set_func inner called start
this is test fun
set_func inner called end
ret value is hello world
两个装饰器装饰一个函数
def set_func(fun1):
    print('----a----')

    def call_func1():
        print('----1----')
        return '<b>' + fun() + '</b>'
    return call_func1


def set_func(fun2):
    print('----b----')

    def call_func2():
        print('----2----')
        return '<i>' + fun() + '</i>'

    return call_func2


@set_func1
@set_func2
def test():
    print('----c----')
    print('----3----')
    return 'life is short,you need python'

ret = test()
print(ret)

输出结果为:

----b----
----a----
----1----
----2----
----c----
----3----
<b><i>hello python decorator</i></b>

程序分析:
程序从上到下执行,执行到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 '<b>' + fun() + '</b>'

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

return '<i>' + fun() + '</i>'

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

'<i>' + 'life is short,you need python' + '</i>'

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

 '<b>' + '<i>' + 'life is short,you need python' + '</i>' + '</b>'

因此最后打印出:

<b><i>hello python decorator</i></b>

所以最后的结果为:

----b----
----a----
----1----
----2----
----c----
----3----
<b><i>hello python decorator</i></b>

附上一张流程图:

带参数的装饰器
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 wangcai
this 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()


@Test
def test():
    print('this is test func')

test()

输出结果:

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

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值