当前位置: 首页 > news >正文

Python基础装饰器的基本原理

1.装饰器

  所谓装饰器一般是对已经使用(上线)的函数增加功能.

  但是因为一般的大公司的严格按照开放封闭原则(对扩展是开放的,对修改是封闭的),不会让你修改原本的函数.

  装饰器就是在不改变原本的函数且不改变原本函数的调用方式上,为原本的函数增加功能的东西.

  所以需求就是:

  1.不改变原函数

  2.不改变原函数的调用方式

  3.增加函数的功能

1.1最基本的装饰器

  假设存在这样的原函数

1 import time
2 def func():
3     time.sleep(0.01)
4     print('func is running!')

  很简单的一个函数,我们现在要为这个原函数增加一个计算函数运行时间的功能.

  我们知道计算函数运行时间的函数可以这么写:

1 def time_func():
2     start = time.time() #记录运行前时间
3     func()              #调用原函数
4     end = time.time() #记录运行结束时间
5     print(end - start)
6 
7 time_func()

  运行结果:

1 func is running!
2 0.01015472412109375

  这样虽然可以算出函数的运行时间,但是对原函数的调用就完全变了,为了让调用不变,我们可以将装饰器函数写成这样:

 1 def time_func(f):
 2     def inner():
 3         start = time.time() #记录运行前时间
 4         f()              #调用原函数
 5         end = time.time() #记录运行结束时间
 6         print(end - start)
 7     return inner    #返回inner的地址
 8 
 9 print(func)         #func函数的地址
10 func = time_func(func)  #将func函数的内存地址发给time_func函数
11 print(func)     #inner函数的地址
12 func()

  运行结果: 

1 <function func at 0x0000023371712E18>
2 <function time_func.<locals>.inner at 0x00000233735867B8>
3 func is running!
4 0.010173320770263672

  可以看到这个装饰器函数实现了通过原函数的调用方式---func()---实现了计算函数的运行时间功能的增加.实现的最基本的装饰器功能.

  分析一下实现过程:

  1.将func函数的内存地址作为参数传给time_func函数,使inner中的f()就相当于func()调用原函数

  2.time_func将inner函数的地址赋值给变量func,让第12行的func()等价于inner()

  使得表面上是:

      func()调用原函数

  实际上是:

      func()等价于inner()

      inner调用f()

      func作为参数传给time_func函数,即f()等价于func()

1.2  有返回值的装饰器

  最开始发原函数太简单了,我们给他增加返回值:

1 import time
2 def func():
3     time.sleep(0.01)
4     print('func is running!')
5     return 'Hello World!'

  通过分析,我们知道前面的func()其实是inner(),而inner函数本身没有返回值,我们需要在inner()中增加接受原函数的返回值并返回的功能,可以写作:

1 def time_func(f):
2     def inner():
3         start = time.time() #记录运行前时间
4         ret = f()              #ret接受原函数的返回值原函数  ----增加的行
5         end = time.time() #记录运行结束时间
6         print(end - start)
7         return ret  #返回原函数的返回值  ----增加的行
8     return inner    #返回inner的地址
1 func = time_func(func) 
2 print(func())

  运行结果:

1 func is running!
2 0.01049661636352539
3 Hello World!

1.3 有参数的装饰器

  有的返回值,还缺什么才比较像基本的函数?    参数

  先来简单一点的,有限个参数的,原函数写作:

1 import time
2 def func(a,b):
3     time.sleep(0.01)
4     print('func is running!',a,b)
5     return 'Hello World!'

  有参数和返回值不是一样么,在inner函数后面加上参数不就得了,装饰器可以写做:

1 def time_func(f):
2     def inner(a,b): #增加参数                 --增加的行
3         start = time.time() #记录运行前时间
4         ret = f(a,b)              #将inner函数接到的参数传给原函数  --增加的行
5         end = time.time() #记录运行结束时间
6         print(end - start)
7         return ret  #返回原函数的返回值
8     return inner    #返回inner的地址
1 func = time_func(func) 
2 print(func('haha','heihei'))   #传入haha和heihei

  运行结果:

1 func is running! haha heihei
2 0.010972023010253906
3 Hello World!

  成功了!

  实际中,原函数的参数可能有很多种,数量也不可能固定不变,不可能原函数的参数一变,你就去改装饰器函数,这样太麻烦

  为了解决参数的问题,我们提出王炸策略

  *args   ------动态参数,可以接受任意数量的位置参数

  **kwargs ----动态参数,可以接受任意数量的关键字参数

  这种策略在python内置函数中多有用到,比如len函数:

1 def len(*args, **kwargs): # real signature unknown
2     """ Return the number of items in a container. """
3     pass

  它能接受一切数量的参数,无论是位置参数还是关键字参数,仿照这个,我们可以写出接受任意参数的装饰器函数

  原函数:

1 import time
2 def func(*args, **kwargs):
3     time.sleep(0.01)
4     print('func is running!',*args, **kwargs)
5     return 'Hello World!'

  装饰器函数:

1 def time_func(f):
2     def inner(*args, **kwargs): #增加参数    ----修改部分
3         start = time.time() #记录运行前时间
4         ret = f(*args, **kwargs)              #将inner函数接到的参数传给原函数-----修改部分
5         end = time.time() #记录运行结束时间
6         print(end - start)
7         return ret  #返回原函数的返回值
8     return inner    #返回inner的地址
1 # print(func)         #func函数的地址
2 func = time_func(func)  #将func函数的内存地址发给time_func函数
3 # print(func)     #inner函数的地址
4 print(func('haha','heihei',1,2,3,4,5,6,3,2,1))

  运行结果:

1 func is running! haha heihei 1 2 3 4 5 6 3 2 1
2 0.010983467102050781
3 Hello World!

1.4  装饰器的固定模式

  前面以求运行时间为例,将其进行推广就可以得到装饰器的固定模式:

1 def wrapper(f):    #装饰器函数,f是被装饰的函数
2     def inner(*args,**kwargs):
3         '''在被装饰函数之前要做的事'''
4         ret = f(*args,**kwargs)    #被装饰的函数
5         '''在被装饰函数之后要做的事'''
6         return ret
7     return inner
8 
9 func = wrapper(func)   #感觉有点多余

  很明显发现第九行看起来有点多余,于是有了语法糖的概念

  语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。----by百度百科

  装饰器的语法糖就是在被装饰函数,也就是原函数的前面加@装饰器函数

  像这样:

1 @wrapper
2 def func(*args, **kwargs):
3     time.sleep(0.01)
4     print('func is running!',*args, **kwargs)
5     return 'Hello World!'

  由于@wrapper 等价于func = wrapper(func)

  而@wrapper要写在原函数上面,按照python解释器从上到下的工作规律,如果装饰器函数在原函数下面,@wrapper相当于没有意义,会报错,所以装饰器函数必须在原函数之前,整体写做这样:

 1 import time
 2 
 3 def wrapper(f):    #装饰器函数,f是被装饰的函数
 4     def inner(*args,**kwargs):
 5         '''在被装饰函数之前要做的事'''
 6         ret = f(*args,**kwargs)    #被装饰的函数
 7         '''在被装饰函数之后要做的事'''
 8         return ret
 9     return inner
10 
11 @wrapper
12 def func(*args, **kwargs):
13     time.sleep(0.01)
14     print('func is running!',*args, **kwargs)
15     return 'Hello World!'
16 print(func('haha','heihei',1,2,3,4,5,6,3,2,1))

  运行结果:

1 func is running! haha heihei 1 2 3 4 5 6 3 2 1
2 Hello World!

 

  

转载于:https://www.cnblogs.com/shuimohei/p/9657362.html

相关文章:

  • 在vue中使用animate.css
  • 集中绕组和分布绕组
  • redis pubsub
  • 【BZOJ4006】管道连接(动态规划,斯坦纳树)
  • 9-18 一次编程面试
  • python学习之路——作业 day6(18/9/18)
  • Kotlin基础学习笔记 (三)
  • 表管理
  • 《手把手教你学DSP-基于TMS320F28335》书中的错误
  • rxjs - 创建数据流
  • Visual-UIElement-FrameworkElement,带来更多功能的同时也带来了更多的限制
  • 源码探究Java_HashMap
  • python-第一个程序
  • 【cs231n】神经网络学习笔记3
  • 实验吧 看起来有点难(手工注入加sqlmap注入)
  • 【跃迁之路】【444天】程序员高效学习方法论探索系列(实验阶段201-2018.04.25)...
  • fetch 从初识到应用
  • GDB 调试 Mysql 实战(三)优先队列排序算法中的行记录长度统计是怎么来的(上)...
  • HomeBrew常规使用教程
  • jquery cookie
  • JS专题之继承
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • 汉诺塔算法
  • 今年的LC3大会没了?
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 如何优雅地使用 Sublime Text
  • 吐槽Javascript系列二:数组中的splice和slice方法
  • 小程序 setData 学问多
  • 《码出高效》学习笔记与书中错误记录
  • Java数据解析之JSON
  • 积累各种好的链接
  • 容器镜像
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • ​Distil-Whisper:比Whisper快6倍,体积小50%的语音识别模型
  • ​一文看懂数据清洗:缺失值、异常值和重复值的处理
  • (¥1011)-(一千零一拾一元整)输出
  • (Mirage系列之二)VMware Horizon Mirage的经典用户用例及真实案例分析
  • (pytorch进阶之路)扩散概率模型
  • (第二周)效能测试
  • (非本人原创)史记·柴静列传(r4笔记第65天)
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016
  • (个人笔记质量不佳)SQL 左连接、右连接、内连接的区别
  • (转)重识new
  • .mysql secret在哪_MySQL如何使用索引
  • .NET 命令行参数包含应用程序路径吗?
  • .NET/C# 使用 ConditionalWeakTable 附加字段(CLR 版本的附加属性,也可用用来当作弱引用字典 WeakDictionary)
  • .NET开源快速、强大、免费的电子表格组件
  • @data注解_SpringBoot 使用WebSocket打造在线聊天室(基于注解)
  • @Data注解的作用
  • @RequestMapping-占位符映射
  • [ SNOI 2013 ] Quare
  • [ 云计算 | AWS 实践 ] Java 如何重命名 Amazon S3 中的文件和文件夹
  • [100天算法】-不同路径 III(day 73)
  • [C#]winform部署yolov5-onnx模型
  • [C#]winform部署yolov9的onnx模型