Python学习—装饰器

学习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)

相关推荐

  • 深入理解 Python 内部函数和闭包(进阶)

    大家好,我是安果! 本文以内部函数为主线,深入讲解内部函数和闭包的应用场景和原理,学会后你的 Python 水平会再上一个台阶,对工作面试或实战应用都会很有帮助 本文包括: 函数是一等公民 内部函数定 ...

  • 一文看懂Python系列之装饰器(decorator)(工作面试必读)

    Python的装饰器(decorator)可以说是Python的一个神器,它可以在不改变一个函数代码和调用方式的情况下给函数添加新的功能.Python的装饰器同时也是Python学习从入门到精通过程中 ...

  • python笔记36-装饰器之wraps

    前言 前面一篇对python装饰器有了初步的了解了,但是还不够完美,领导看了后又提出了新的需求,希望运行的日志能显示出具体运行的哪个函数. name和doc __name__用于获取函数的名称,__d ...

  • 计算运行时间-装饰器实现

    装饰器的一个使用场景 之前有人问装饰器有什么用,能不能具体说个场景.当时,没想好如何使用这个问题.昨天看github上有一个纯python实现的有序数据结构库-SortedContainer.说的很牛 ...

  • 初识装饰器函数

    我之前看装饰器文章介绍,很少有用 装饰器函数这种称谓的.但是今天大邓简单的学了下装饰器,觉得应该先让大家知道装饰器是一种函数,让大家从熟悉的函数去学习装饰器.大邓姑且草率的将其称呼为 装饰器函数 装饰 ...

  • 神奇的Python property装饰器:1行代码让Python方法秒变属性

    神奇的Python property装饰器:1行代码让Python方法秒变属性

  • 第20天:Python 之装饰器

    第20天:Python 之装饰器

  • 一文看懂Python的装饰器

    在 Python 中使用装饰器,可以在不修改代码的前提下,为已有的函数添加新功能,例如打印日志.缓存数据等. 为什么需要装饰器 假如你要为某个函数添加新功能.直接的办法是,在该函数中实现这个功能,或者 ...

  • Python 中的函数装饰器和闭包

    函数装饰器可以被用于增强方法的某些行为,如果想自己实现装饰器,则必须了解闭包的概念. 装饰器的基本概念 装饰器是一个可调用对象,它的参数是另一个函数,称为被装饰函数.装饰器可以修改这个函数再将其返回, ...

  • Selenium2+python自动化55-unittest之装饰器(@classmethod)

    前言 前面讲到unittest里面setUp可以在每次执行用例前执行,这样有效的减少了代码量,但是有个弊端,比如打开浏览器操作,每次执行用例时候都会重新打开,这样就会浪费很多时间. 于是就想是不是可以 ...

  • 浅析Python装饰器

    浅析Python装饰器

  • 说说在 Python 中如何实现输出指定函数运行时长的装饰器

    假设我们需要一个可以输出某个函数运行时长的装饰器. 1 基础实现 一种可能的定义方式为: 这里利用函数装饰器,在 clock(func) 函数内部定义了一个 clock(*args) 函数,定义好后直 ...

  • 为什么 Python 没有函数重载?如何用装饰器实现函数重载?

    英文:https://arpitbhayani.me/blogs/function-overloading 作者:arprit 译者:豌豆花下猫("Python猫"公众号作者) 声 ...

  • 【进阶】一文读懂Python装饰器,搞清来龙去脉!

    (给机器学习算法与Python学习加星标,提升AI技能) 选自pouannes.github.io 作者:Pierre Ouannes 本文由机器之心(nearhuman2014)翻译 原文:http ...