python装饰器原理与用法深入详解


Posted in Python onDecember 19, 2019

本文实例讲述了python装饰器原理与用法。分享给大家供大家参考,具体如下:

你会Python嘛?
我会!
那你给我讲下Python装饰器吧!
Python装饰器啊?我没用过哎

以上是我一个哥们面试时候发生的真实对白。

----------------------------------------------分割线------------------------------------------------------------------------------

简言之,python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,使用python装饰器的好处就是在不用更改原函数的代码前提下给函数增加新的功能。
一般而言,我们要想拓展原来函数代码,最直接的办法就是侵入代码里面修改,例如:

import time
def func():
  print("hello")
  time.sleep(1)
  print("world")

这是我们最原始的的一个函数,然后我们试图记录下这个函数执行的总时间,那最简单的做法就是:

#原始侵入,篡改原函数
import time
def func():
  startTime = time.time()
  print("hello")
  time.sleep(1)
  print("world")
  endTime = time.time()
  msecs = (endTime - startTime)*1000
  print("time is %d ms" %msecs)

但是如果你的Boss在公司里面和你说:“小祁,这段代码是我们公司的核心代码,你不能直接去改我们的核心代码。”那该怎么办呢,我们仿照装饰器先自己试着写一下:

#避免直接侵入原函数修改,但是生效需要再次执行函数
import time
def deco(func):
  startTime = time.time()
  func()
  endTime = time.time()
  msecs = (endTime - startTime)*1000
  print("time is %d ms" %msecs)
def func():
  print("hello")
  time.sleep(1)
  print("world")
if __name__ == '__main__':
  f = func
  deco(f)#只有把func()或者f()作为参数执行,新加入功能才会生效
  print("f.__name__ is",f.__name__)#f的name就是func

这里我们定义了一个函数deco,它的参数是一个函数,然后给这个函数嵌入了计时功能。然后你可以拍着胸脯对老板说,看吧,不用动你原来的代码,我照样拓展了它的函数功能。

然后你的老板有对你说:“小祁,我们公司核心代码区域有一千万个func()函数,从func01()到func1kw(),按你的方案,想要拓展这一千万个函数功能,就是要执行一千万次deco()函数,这可不行呀,我心疼我的机器。”

好了,你终于受够你老板了,准备辞职了,然后你无意间听到了装饰器这个神器,突然发现能满足你闫博士的要求了。

我们先实现一个最简陋的装饰器,不使用任何语法糖和高级语法,看看装饰器最原始的面貌:

#既不需要侵入,也不需要函数重复执行
import time
def deco(func):
  def wrapper():
    startTime = time.time()
    func()
    endTime = time.time()
    msecs = (endTime - startTime)*1000
    print("time is %d ms" %msecs)
  return wrapper
@deco
def func():
  print("hello")
  time.sleep(1)
  print("world")
if __name__ == '__main__':
  f = func #这里f被赋值为func,执行f()就是执行func()
  f()

这里的deco函数就是最原始的装饰器,它的参数是一个函数,然后返回值也是一个函数。其中作为参数的这个函数func()就在返回函数wrapper()的内部执行。然后在函数func()前面加上@deco,func()函数就相当于被注入了计时功能,现在只要调用func(),它就已经变身为“新的功能更多”的函数了。

所以这里装饰器就像一个注入符号:有了它,拓展了原来函数的功能既不需要侵入函数内更改代码,也不需要重复执行原函数。

#带有参数的装饰器
import time
def deco(func):
  def wrapper(a,b):
    startTime = time.time()
    func(a,b)
    endTime = time.time()
    msecs = (endTime - startTime)*1000
    print("time is %d ms" %msecs)
  return wrapper
@deco
def func(a,b):
  print("hello,here is a func for add :")
  time.sleep(1)
  print("result is %d" %(a+b))
if __name__ == '__main__':
  f = func
  f(3,4)
  #func()

然后你满足了Boss的要求后,Boss又说:“小祁,我让你拓展的函数好多可是有参数的呀,有的参数还是个数不定的那种,你的装饰器搞的定不?”然后你嘿嘿一笑,深藏功与名!

#带有不定参数的装饰器
import time
def deco(func):
  def wrapper(*args, **kwargs):
    startTime = time.time()
    func(*args, **kwargs)
    endTime = time.time()
    msecs = (endTime - startTime)*1000
    print("time is %d ms" %msecs)
  return wrapper
@deco
def func(a,b):
  print("hello,here is a func for add :")
  time.sleep(1)
  print("result is %d" %(a+b))
@deco
def func2(a,b,c):
  print("hello,here is a func for add :")
  time.sleep(1)
  print("result is %d" %(a+b+c))
if __name__ == '__main__':
  f = func
  func2(3,4,5)
  f(3,4)
  #func()

最后,你的老板说:“可以的,小祁,我这里一个函数需要加入很多功能,一个装饰器怕是搞不定,装饰器能支持多个嘛"

最后你就把这段代码丢给了他:

#多个装饰器
import time
def deco01(func):
  def wrapper(*args, **kwargs):
    print("this is deco01")
    startTime = time.time()
    func(*args, **kwargs)
    endTime = time.time()
    msecs = (endTime - startTime)*1000
    print("time is %d ms" %msecs)
    print("deco01 end here")
  return wrapper
def deco02(func):
  def wrapper(*args, **kwargs):
    print("this is deco02")
    func(*args, **kwargs)
    print("deco02 end here")
  return wrapper
@deco01
@deco02
def func(a,b):
  print("hello,here is a func for add :")
  time.sleep(1)
  print("result is %d" %(a+b))
if __name__ == '__main__':
  f = func
  f(3,4)
  #func()
'''
this is deco01
this is deco02
hello,here is a func for add :
result is 7
deco02 end here
time is 1003 ms
deco01 end here
'''

多个装饰器执行的顺序就是从最后一个装饰器开始,执行到第一个装饰器,再执行函数本身。

盗用评论里面一位童鞋的例子:

def dec1(func):
  print("1111")
  def one():
    print("2222")
    func()
    print("3333")
  return one
def dec2(func):
  print("aaaa")
  def two():
    print("bbbb")
    func()
    print("cccc")
  return two
@dec1
@dec2
def test():
  print("test test")
test()

输出:

aaaa
1111
2222
bbbb
test test
cccc
3333

装饰器的外函数和内函数之间的语句是没有装饰到目标函数上的,而是在装载装饰器时的附加操作。

17~20行是装载装饰器的过程,相当于执行了test=dect1(dect2(test)),此时先执行dect2(test),结果是输出aaaa、将func指向函数test、并返回函数two,然后执行dect1(two),结果是输出1111、将func指向函数two、并返回函数one,然后进行赋值。

用函数替代了函数名test。 22行则是实际调用被装载的函数,这时实际上执行的是函数one,运行到func()时执行函数two,再运行到func()时执行未修饰的函数test。

希望本文所述对大家Python程序设计有所帮助。

Python 相关文章推荐
Python压缩和解压缩zip文件
Feb 14 Python
python使用多线程不断刷新网页的方法
Mar 31 Python
Python实现的数据结构与算法之双端队列详解
Apr 22 Python
Python实现的视频播放器功能完整示例
Feb 01 Python
Python 元类实例解析
Apr 04 Python
mvc框架打造笔记之wsgi协议的优缺点以及接口实现
Aug 01 Python
Python中pymysql 模块的使用详解
Aug 12 Python
Pytorch实现各种2d卷积示例
Dec 30 Python
对pytorch的函数中的group参数的作用介绍
Feb 18 Python
pandas中的ExcelWriter和ExcelFile的实现方法
Apr 24 Python
python 获取谷歌浏览器保存的密码
Jan 06 Python
聊聊Python中关于a=[[]]*3的反思
Jun 02 Python
python列表生成器迭代器实例解析
Dec 19 #Python
Python tensorflow实现mnist手写数字识别示例【非卷积与卷积实现】
Dec 19 #Python
Python: 传递列表副本方式
Dec 19 #Python
python内置模块collections知识点总结
Dec 19 #Python
Python操作redis和mongoDB的方法
Dec 19 #Python
Python 实现Serial 与STM32J进行串口通讯
Dec 18 #Python
实现Python与STM32通信方式
Dec 18 #Python
You might like
php解决约瑟夫环示例
2014/04/09 PHP
PHP之图片上传类实例代码(加了缩略图)
2016/06/30 PHP
PHP 中 var_export、print_r、var_dump 调试中的区别
2018/06/19 PHP
网页里控制图片大小的相关代码
2006/06/13 Javascript
在Javascript中定义对象类别
2006/12/22 Javascript
使用prototype.js 的时候应该特别注意的几个问题.
2007/04/12 Javascript
javascript 去字符串空格终极版(支持utf8)
2009/11/14 Javascript
Extjs学习笔记之一 初识Extjs之MessageBox
2010/01/07 Javascript
数组Array进行原型prototype扩展后带来的for in遍历问题
2010/02/07 Javascript
javascript 系统文件夹文件操作及参数介绍
2013/01/08 Javascript
jquery插件star-rating.js实现星级评分特效
2015/04/15 Javascript
浅谈JQuery+ajax+jsonp 跨域访问
2016/06/25 Javascript
深入浅出ES6新特性之函数默认参数和箭头函数
2016/08/01 Javascript
Bootstrap栅格系统学习笔记
2016/11/25 Javascript
JavaScript之RegExp_动力节点Java学院整理
2017/06/29 Javascript
node.js中grunt和gulp的区别详解
2017/07/17 Javascript
JS实现的抛物线运动效果示例
2018/01/30 Javascript
mpvue小程序仿qq左滑置顶删除组件
2018/08/03 Javascript
详解angular2如何手动点击特定元素上的点击事件
2018/10/16 Javascript
详解如何用webpack4从零开始构建react开发环境
2019/01/27 Javascript
javascript设计模式 ? 适配器模式原理与应用实例分析
2020/04/13 Javascript
解决Mint-ui 框架Popup和Datetime Picker组件滚动穿透的问题
2020/11/04 Javascript
vant-ui框架的一个bug(解决切换后onload不触发)
2020/11/11 Javascript
[02:33]DOTA2英雄基础教程 司夜刺客
2013/12/04 DOTA
[04:40]2016国际邀请赛中国区预选赛全程TOP10镜头集锦
2016/07/01 DOTA
利用python批量检查网站的可用性
2016/09/09 Python
python如何获取服务器硬件信息
2017/05/11 Python
Python 实现毫秒级淘宝抢购脚本的示例代码
2019/09/16 Python
keras 实现轻量级网络ShuffleNet教程
2020/06/19 Python
Python中免验证跳转到内容页的实例代码
2020/10/23 Python
出生证明公证书
2014/04/09 职场文书
夫妻双方自愿离婚协议书怎么写
2014/12/01 职场文书
小学教师2014年度工作总结
2014/12/03 职场文书
初中优秀学生评语
2014/12/29 职场文书
中班下学期个人工作总结
2015/02/12 职场文书
CSS3实现的侧滑菜单
2021/04/27 HTML / CSS