装饰器

通过装饰器可以为函数添加附加功能。通过修改函数本身也可以实现增加功能,但是通过装饰器还有下面2个好处。装饰器的2个原则:不改变被装饰函数的源代码

不改变被装饰函数的调用方式

遵循了上面2个原则,我们可以在为函数添加附加功能的时候,不必去破坏已有的稳定的代码和代码调用方式。并且易于回退。

先定义一个函数:import time

def sleep():

"运行后等待2秒"

print("wait for 2 seconds")

time.sleep(2)

print("END")

sleep()

现在我希望为这个函数附加一个功能,计算这个函数运行的时间,但是我不希望修改sleep函数的源代码,所以我们再定义一个函数来附加这个功能import time

def sub_a(func):

"计算参数的函数的运行时间"

t = time.time() # 记录当前时间戳

func()

print(time.time() - t) # 现在的时间戳和之前的时间戳的差值就是中间这个函数的运行时间

def sleep():

"运行后等待2秒"

print("wait for 2 seconds")

time.sleep(2)

print("END")

sub_a(sleep)

我们未修改sleep函数,而是定义了一个新函数。通过这个新的函数执行附加的代码并且用参数调用执行被装饰的函数。

但是这里我们的调用方式变了,现在需要调用新的函数,并且要使用原来的调用方式sleep()来调用。

在之前的基础上,我们给装饰器外面再套上一层函数。先看代码,下面再分析实现过程。import time

def sub_b(func):

def sub_a():

"计算参数的函数的运行时间"

t = time.time()

func()

print(time.time() - t)

return sub_a # 返回sub_a函数的内存地址

def sleep():

"运行后等待2秒"

print("wait for 2 seconds")

time.sleep(2)

print("END")

sleep = sub_b(sleep)

sleep()

sleep = sub_b(sleep)语句赋值后,sleep的值是sub_b函数的返回值,也就是sub_a的内存地址,而且sub_a里的func也已经赋了sleep的内存地址(在调用sub_b的时候作为参数传入了sleep的内存地址)。

最后我们再sleep()实际调用的是sub_a(),而sub_a里的func()执行的是sleep()。附加代码和sleep的源代码都执行了,并且最后的调用方式也没变。

这里再插入解释一个小问题,sub_a现在只是sub_b函数里的一个子函数,局部变量。也就是调用完sub_b之后,sub_a是会被释放的。

但是由于sub_b最后的return sub_a返回了sub_a的内存地址,并且在sleep=sub_b(sleep),将这个内存地址又赋值给了sleep。所以在sub_b调用结束后,sub_a还再被sleep占用着,所以并没有释放sub_a。

至此不改变调用方式的原则也实现了。

再看看参数,之前使用的函数都没有参数,如果为sleep()函数加一个参数,那么代码就报错。所以目前的装饰器只能装饰不带参数的函数,要适应各种参数类型的函数,我们需要使用不固定参数import time

def sub_b(func):

def sub_a(*args,**kwargs): # 这里无脑的加上不固定参数

"计算参数的函数的运行时间"

t = time.time()

func(*args,**kwargs) # 将所有参数都传给被装饰函数

print(time.time() - t)

return sub_a

def sleep(n): # 现在我们换一个有参数的函数来装饰它

"运行后等待n秒"

print("wait for %d seconds"%n)

time.sleep(n)

print("END")

sleep = sub_b(sleep)

sleep(2)

简单无脑的加上不固定参数就行了,具体是什么参数,具体有多少参数我们不用关心,只要全部传过去就行了。

如此,参数也不是问题了。还有一个返回值的问题,将sleep修改成一个有返回值的函数。又会有问题,这次不报错,但是返回值是None。

再修改,调用被装饰函数时,将返回值保存。装饰器最后再return这个值给装饰器。import time

def sub_b(func):

def sub_a(*args,**kwargs):

"计算参数的函数的运行时间"

t = time.time()

res = func(*args,**kwargs) # 将被装饰函数的返回值保存

print(time.time() - t)

return res # 将之前保存的值在这里返回,作为装饰器的返回值

return sub_a

def sleep(n):

"运行后等待n秒"

print("wait for %d seconds"%n)

time.sleep(n)

return "END"

sleep = sub_b(sleep)

print(sleep(2))

现在返回值也有了。东西都全了,但是和别人的装饰器还有一点点不一样。也就差了两句。别人的装饰器大概这下面这个样子的。import time

def sub_b(func):

def sub_a(*args,**kwargs):

"计算参数的函数的运行时间"

t = time.time()

res = func(*args,**kwargs)

print(time.time() - t)

return res

return sub_a

@sub_b # 添加这一句

def sleep(n):

"运行后等待n秒"

print("wait for %d seconds"%n)

time.sleep(n)

return "END"

#sleep = sub_b(sleep) # 去掉这一句

print(sleep(2))

这里使用了@语句,并且放到了被装饰函数前面。替代了原本在被装饰函数后面的赋值语句。这两句效果是一样的。

但是@语句明显代码更加简洁,并且位置是在被装饰函数前,更加易于阅读。

不过后面还会继续使用赋值的形式,我觉得更好理解装饰器的实现方式。

装饰器实际也是一个函数,那么函数就可以带参数,通过参数,可以使函数有更多的选项。现在我们要给装饰器也加上参数。

之前实现不改变调用方式的时候,我们为装饰器套了一层。现在为了让装饰器能够带上参数,我们需要再套一层,具体原理也不是很明白,先看三层的代码:import time

def sub_c(): # 先不写参数,将来参数可以填这里了

def sub_b(func):

def sub_a(*args,**kwargs):

"计算参数的函数的运行时间"

t = time.time()

res = func(*args,**kwargs)

print(time.time() - t)

return res

return sub_a

return sub_b

def sleep(n):

"运行后等待n秒"

print("wait for %d seconds"%n)

time.sleep(n)

return "END"

sleep = sub_c()(sleep)

print(sleep(2))

先看一下赋值的意义,sleep=sub_c()(sleep)。sub_c()的值就是他的返回值sub_b,也就是sub_b函数的内存地址。后面再跟上(sleep),就是执行sub_b(sleep)。也就是说sleep=sub_b(sleep)。如此剥去了最外面一层,就和上面两层的赋值一样了。

所以三层装饰器用的时候,sub_c后面多了一个(),因为这里我们要的是最外层函数的返回值。而二层装饰器用的时候,后面是没有()的,这里要的是函数代码块的内存地址。

最后,我们就可以方便的为装饰器带上参数了。我们加上可以控制浮点小数位数的参数。import time

def sub_c(n):

def sub_b(func):

def sub_a(*args,**kwargs):

"计算参数的函数的运行时间"

t = time.time()

res = func(*args,**kwargs)

print(round((time.time() - t),n))

return res

return sub_a

return sub_b

def sleep(n):

"运行后等待n秒"

print("wait for %d seconds"%n)

time.sleep(n)

return "END"

sleep = sub_c(8)(sleep)

print(sleep(2))

最后还是要用@的形式

总结

最后是2个装饰器最终的模板,按下面说的调整一下,就算不理解应该也能套用了。将最外层的函数名改成你的装饰器的名字,为你的装饰器起一个合适的名字

内层的函数名无所谓

最内层的代码块做替换成你自己的代码,res=func(*args,**kwargs)和最后的return res这两句不用修改

两层装饰器模板:import time

def run_time(func): # 最外层的函数名字换成你装饰器的名字

def wrapper(*args,**kwargs):

"计算参数的函数的运行时间"

t = time.time() # 这里替换成你的代码段

res = func(*args,**kwargs) # 这句不变

print(time.time() - t) # 这里换成你的代码段

return res # 这句不变

return wrapper

@run_time

def sleep(n):

"运行后等待n秒"

print("wait for %d seconds"%n)

time.sleep(n)

return "END"

print(sleep(2))

三层装饰器模板:import time

def run_time(n): # 最外层的函数名字换成你装饰器的名字

def decorator(func): # 内层的函数名字无所谓

def wrapper(*args,**kwargs):

"计算参数的函数的运行时间"

t = time.time() # 这里替换成你的代码段

res = func(*args,**kwargs) # 这句不变

print(round((time.time() - t),n)) # 这里替换成你的代码段

return res # 这句不变

return wrapper

return decorator

@run_time(8)

def sleep(n):

"运行后等待n秒"

print("wait for %d seconds"%n)

time.sleep(n)

return "END"

print(sleep(2))

python三层装饰器-Python自动化开发学习4-装饰器相关推荐

  1. python自动化可以做什么菜_Python自动化开发学习之三级菜单制作

    本文实例为大家分享了Python三级菜单展示的具体代码,供大家参考,具体内容如下 作业需求: (1)运行程序输出第一级菜单 (2)选择一级菜单某项,输出二级菜单,同理输出三级菜单 (3)让用户选择是否 ...

  2. Python自动化开发学习的第九周----线程、进程、协程

    一.计算机操作系统的简介 手工操作(无操作系统) 1946年第一台计算机诞生--20世纪50年代中期,还未出现操作系统,计算机工作采用手工操作方式. 程序员将对应于程序和数据的已穿孔的纸带(或卡片)装 ...

  3. Python自动化开发学习22-Django上

    session 上节已经讲了使用Cookie来做用户认证,但是 Cookie的问题 缺点:敏感信息不适合放在cookie里,敏感信息只能放在服务器端 优势:把部分用户数据分散的存放在每个客户端,减轻服 ...

  4. Python自动化开发学习的第十一周----WEB框架--Django基础

    WEB框架的本质 对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. #!/usr/bin/env python #coding:utf-8imp ...

  5. Python自动化开发学习6

    引子 假设我们要在我们的程序里表示狗,狗有如下属性:名字.品种.颜色.那么可以先定义一个模板,然后调用这个模板生成各种狗. def dog(name,d_type,color):data = {'na ...

  6. python自动化开发是什么_Python自动化开发学习6

    引子 假设我们要在我们的程序里表示狗,狗有如下属性:名字.品种.颜色.那么可以先定义一个模板,然后调用这个模板生成各种狗. def dog(name,d_type,color): data = { ' ...

  7. Python自动化开发学习13-堡垒机开发

    堡垒机介绍 很多人觉得,堡垒机就是跳板机,这是不全面的.跳板功能只是堡垒机所具备的功能的其中一项.堡垒机还有以下两个至关重要的功能: 权限管理 : 用户使用堡垒机账号登录堡垒机系统.不需要知道别的主机 ...

  8. python自动化办公都能做什么菜-Python自动化开发学习之三级菜单制作

    本文实例为大家分享了Python三级菜单展示的具体代码,供大家参考,具体内容如下 作业需求: (1)运行程序输出第一级菜单 (2)选择一级菜单某项,输出二级菜单,同理输出三级菜单 (3)让用户选择是否 ...

  9. Python自动化开发学习15-css补充内容

    上节回顾 上一节学习的内容,有一下几点,可以注意一下.或者说推荐这么做. class可以设置多个值-css样式重用 可以给一个标签设置多个class值,这样我们可以为每个class应用一种样式.标签有 ...

最新文章

  1. 多个linux发行版本混合安装盘,使用 MultiBootUSB 安装多个 Linux 版本
  2. C语言程序设计省二考试,浙江省高校计算机等级考试大纲(二级——C语言程序设计大纲)(...
  3. 光流 | OpenCV中的Lucas-Kanade光流与稠密光流:基于Opencv+Python(附代码)
  4. 修改用户名_新华美育查找用户名及修改密码的方法分享
  5. Hibernate初探(二)
  6. #define的高级用法
  7. 谁顶住双11的世界级流量洪峰?神龙架构负责人等9位大牛现场拆解阿里秘籍
  8. 2018年宝鸡市三检文科数学题目解答
  9. 日周月筛选器_Excel数据筛选与高级筛选,你会用吗
  10. 阿里云Kubernetes SpringCloud 实践进行时(5): 弹性服务与容错处理
  11. winyyy sys hcpidesk sys mtlrd sys uldfhjfh sys servets exe等1
  12. 用小乌龟git解决冲突之后,再提交,出现自己没用动过的文件
  13. 天正安装autocad启动失败_天正cad启动失败
  14. 一、Spring Boot整合redies
  15. cif t t操作流程图_外贸跟单员的详细工作流程:跟单员工作流程图
  16. 天马行空 | 假如上网装X需要花钱?
  17. centOS7安装redis单例配置主从+哨兵+VIP
  18. 六年一轮回:大数据改变的,不仅仅是我的专业!
  19. WQ7033开发指南(按键篇)之4.3 三轴加速度传感器SC7A20驱动导入按键流程详解
  20. mysql sql patindex_深入SQL截取字符串(substring与patindex)的详解

热门文章

  1. [Java拾遗四]JavaWeb基础之Servlet_RequestResponse
  2. java.math.BigDecimal保留两位小数,保留小数,精确位数
  3. select * from table with(nolock)
  4. 类的练习2——python编程从入门到实践
  5. 模块讲解----time与date time(时间模块)
  6. Hive数据仓库之快速入门
  7. 99. Recover Binary Search Tree (Tree; DFS)
  8. php中的MVC模式运用
  9. 机器学习笔记(二)逻辑回归和正则化
  10. 浅谈ANR及log分析ANR