分享

Python学习—装饰器

 看见就非常 2020-10-30

学习Python已经有一段时间了,陆续学了一些基础部分,但是理解的不是很深刻,每过一段时间就会忘记,所以不得不写一些博客进行记录,加深自己的理解。这两个星期一直在研究装饰器,开始觉得很简单,但是只知其然,真要写一个装饰器却又不知如何下手了,显然并没有真正理解装饰器的实现过程。经过两个星期的学习终于觉得自己对装饰器的理解达到了一定的深度,因此 必须记录一下。那就开始吧!

为什么需要装饰器:存在一种需求就是,有一个函数,在多个地方使用或者提供给项目组的他人使用,现在需要为此函数增加一些功能,但是你不能修改函数的代码,也不能重写一个替代函数,然后让别人重新使用你的替代函数,怎么办?装饰器就是干这个的。

装饰器:本质是一个函数,可在不改变原函数代码与调用方式的情况下,为函数增加功能。

解装饰器的实现需要以下几个概念:

1、函数名就是一个变量,使用def方式定义一个函数时,就是将函数名与函数体建立一个指向关系,函数名对应的值就是函数体在内存中的地址;既然函数名是一个变量,那它就具有变量的特性,可以被修改,注意不是函数名被修改,而是函数名对应的值可以被修改

2、高阶函数:高阶函数就是把函数当做参数传递,因为函数名也是变量,所以可把函数名当做参数传递,又因为函数名对应的值是是函数体,因此可以通过传入的函数名参数使用传入的函数。

3、函数闭包:把函数名当做一个值返回,成为函数闭包;一般在一个函数的再部在定义一个函数,那么在外层函数的外面是不可以使用函数内的局部函数的,但是利用闭包,把内部函数以函数名作为返回值,那么就可以做到在函数外使用内部函数。关于函数的闭包,将另写一份博客。

下面详细说明装饰器的实现过程:

  1. import time
  2. def luke():
  3. time.sleep(2)
  4. print('in function luke')

现在要实现一个装饰器,在执行luke函数时计算其执行时间

既然装饰器要装饰一个函数,就必须有一个参数,这个参数用来接收被装饰的函数的函数名

第一步定义一个装饰器函数,此函数有一个参数func,func用于接收一个函数名

  1. def caculate_func_run_time(func):
  2. pass

第二部实现函数执行的时间计算:

  1. def caculate_func_run_time(func):
  2. start_time = time.time()
  3. func()
  4. end_time = time.time()
  5. print('func run time is %s'%(end_time - start_time))

第三步,利用函数闭包,在装饰器内部定义一个函数,将函数体包装,然后返回装饰器内部的包装函数warpper

  1. def caculate_func_run_time(func):
  2. def warpper():
  3. start_time = time.time()
  4. func()
  5. end_time = time.time()
  6. print('func run time is %s'%(end_time - start_time))
  7. return warpper # 注意此处返回的是函数名,没有'()'

第四步,利用函数名即变量的特性,修改luke函数名对应的值

luke = caculate_func_run_time(luke)

经过此步之后,luke函数名不变,但是其值对应的已不再是原始函数体,而是caculate_func_run_time函数内部包装后的返         回值即warpper函数名对应的值。

luke = caculate_func_run_time(luke)

luke()

以上两步调用执行时会打印luke函数执行时间,这样虽然没有改变luke原始函数代码,但是改变了函数调用方式,即需要先一步执行luke = caculate_func_run_time(luke),然后执行luke(),才能达到需要的效果,怎么办呢?

python的装饰器(语法糖)就派上用处了,使用以下语法:

  1. @caculate_func_run_time # 此语法的含义是: luke = caculate_func_run_time(luke)
  2. def luke():
  3. time.sleep(2)
  4. print('in function luke')

所谓语法糖,我的理解就是,@符号后的函数名将下一行的函数名当作一颗糖(参数)吃掉(进行执行过程),然后再吐出(返回)一个函数名还给你,即相当于执行这一条语句:luke = caculate_func_run_time(luke)

函数caculate_func_run_time 已经是一个装饰器了,但是有一个问题,原始函数若存在参数或者返回值,现在的装饰器并不能接收原始函数的参数或者返回原始函数的返回值,怎么办呢?

我们需要修改装饰器,使其可以接收原始函数的参数,并返回原始函数的返回值

  1. def caculate_func_run_time(func):
  2. def warpper(*args, **kwargs):
  3. start_time = time.time()
  4. res = func(*args, **kwargs)
  5. end_time = time.time()
  6. print('func run time is %s'%(end_time - start_time))
  7. return res
  8. return warpper
  9. @caculate_func_run_time # 此语法的含义是: luke = caculate_func_run_time(luke)
  10. def luke(sleep_time):
  11. time.sleep(sleep_time)
  12. print('in function luke')
  13. return 1
  14. luke(3)

以上装饰器用res 接收func的执行结果,最后返回,这就做到了返回原始函数的返回值

下面需要理解的是谁接收的luke函数的参数呢?

是warpper函数接收luke函数参数,为什么?

@caculate_func_run_time   ,此语法的含义是: luke = caculate_func_run_time(luke),luke函数名变量的值被修改,那这个值是什么呢?

是caculate_func_run_time(luke)执行后的返回值,即warpper

因此luke == warpper

luke(3)函数调用就是warpper(3)函数调用,所以就是warpper函数接收了luke函数的参数

至于在定义warpper函数使用(*args, **kwargs),是因为使用组参数与关键字参数结合,可以达到接收任意参数的效果,

因此以上装饰器函数可以装饰任意函数,达到计算函数执行时间的效果。

为什么(*args, **kwargs),组参数与关键字参数结合可达到接收任意参数,请参见python学习之函篇的博客。

那么至此还有一个问题,装饰器可不可以有参数呢?当然可以有,但是怎么实现呢?

无参装饰器只是两层包装,内层使用闭包,要实现有参装饰器需要再次使用闭包进行第三层包装,这样内部两层就可以使用有参装饰器的参数。

  1. def caculate_func_run_time(*args, **kwargs)
  2. # 在此函数内部任意位置均可使用装饰器传入的参数(*args, **kwargs)
  3. print(*args, **kwargs)
  4. def out_warpper(func):
  5. def warpper(*args, **kwargs):
  6. start_time = time.time()
  7. res = func(*args, **kwargs)
  8. end_time = time.time()
  9. print('func run time is %s'%(end_time - start_time))
  10. return res
  11. return warpper
  12. return out_warpper
  13. @caculate_func_run_time('有参装饰器')
  14. def luke(sleep_time):
  15. time.sleep(sleep_time)
  16. print('in function luke')
  17. return 1

@caculate_func_run_time('有参装饰器') ——语法糖的执行步骤是:

1、执行:caculate_func_run_time('有参装饰器'),返回一个out_warpper

2、执行:out_warpper(luke),返回一个warpper

3、更新原始函数:luke = warpper

至此就做到了有参装饰器的实现,三层的装饰器已经可以处理任意参数的装饰器,因此也就不再需要第四层的闭包封装。

那么小伙伴们还想不想了解一下更高级的多级装饰,这与三层的装饰器可不一样哦。看看下面的代码吧!

小伙伴们觉得应该怎样输出呢?

  1. def wrapper1(func):
  2. def inner():
  3. print('w1,before')
  4. func()
  5. print('w1,after')
  6. return inner
  7. def wrapper2(func):
  8. def inner():
  9. print('w2,before')
  10. func()
  11. print('w2,after')
  12. return inner
  13. @wrapper2
  14. @wrapper1
  15. def foo():
  16. print('foo')
  17. foo()

下面我们一步步分析吧:

1、当python解释器执行@warpper2时,实质是执行foo = warpper2(@warpper1),要执行此条调用就必须先执行@warpper1,因为warpper2需要一个实参值

2、执行@warpper1就是普通2层装饰器,将foo函数包装在warpper1 内,由inner返回,我们可以看做是warpper1_innner

3、执行warpper2(warpper1_innner),warpper2实际包装的是warpper1返回的inner函数,由warpper2 的inner返回,    warpper2内的inner函数执行func()时,实际执行的是warpper1的innner函数,返回的函数我们可以看做是    warpper2_inner,此时foo函数即为warpper2_inner

4、执行foo()函数,即要先执行warpper2返回的inner函数,首先输出“w2,before”;然后执行func();此函数是warpper2的  参数接收的warpper1的inner函数

5、执行warpper2->inner的func函数,即执行warpper1的inner函数,那么先输出“w1,before”;接着执行func函数,此函数即是warpper1函数的参数接收的foo函数,因此接着输出“foo”,warpper1内的inner函数内的func函数执行完毕后,再输出“w1,after”,至此warpper1的inner函数执行完毕返回,即warpper2内的inner函数内的func函数执行完毕返回,接着输出“w2,after”

因此输出顺序是:

  1. w2,before
  2. w1,before
  3. foo
  4. w1,after
  5. w2,after

朋友们,如果是三级装饰呢,相信小伙伴们已经没有多大问题了。至此关于Python装饰器总结完毕,有大神路过的时候,瞅一眼,有什么问题请批评指正!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多