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之编写类之三子类
Oct 11 Python
Python中字典的基础知识归纳小结
Aug 19 Python
python实现发送邮件及附件功能
Mar 02 Python
Django+Xadmin构建项目的方法步骤
Mar 06 Python
python GUI实现小球满屏乱跑效果
May 09 Python
Django框架 Pagination分页实现代码实例
Sep 04 Python
检测tensorflow是否使用gpu进行计算的方式
Feb 03 Python
Jupyter notebook如何修改平台字体
May 13 Python
Python新手如何进行闭包时绑定变量操作
May 29 Python
Django+Uwsgi+Nginx如何实现生产环境部署
Jul 31 Python
Django正则URL匹配实现流程解析
Nov 13 Python
Python字符串的15个基本操作(小结)
Feb 03 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获得用户使用的代理服务器ip即真实ip
2006/12/31 PHP
Thinkphp单字母函数使用指南
2016/05/08 PHP
Thinkphp批量更新数据的方法汇总
2016/06/29 PHP
JavaScript 提升运行速度之循环篇 译文
2009/08/15 Javascript
jquery 学习之二 属性(html()与html(val))
2010/11/25 Javascript
30个最佳jQuery Lightbox效果插件分享
2011/04/11 Javascript
JavaScript高级程序设计 读书笔记之十一 内置对象Global
2012/03/07 Javascript
动态加载dtree.js树treeview(示例代码)
2013/12/17 Javascript
JavaScript类属性的访问方式详解
2014/02/11 Javascript
js实现浏览本地文件并显示扩展名的方法
2015/08/17 Javascript
nodejs创建web服务器之hello world程序
2015/08/20 NodeJs
jQuery实现向下滑出的平滑下拉菜单效果
2015/08/21 Javascript
jquery实现适用于门户站的导航下拉菜单效果代码
2015/08/24 Javascript
JavaScript String 对象常用方法详解
2016/05/13 Javascript
jquery+CSS3实现3D拖拽相册效果
2016/07/18 Javascript
深入理解bootstrap框架之第二章整体架构
2016/10/09 Javascript
浅析JavaScript的几种Math函数,random(),ceil(),round(),floor()
2016/12/22 Javascript
微信小程序实现点击返回顶层的方法
2017/07/12 Javascript
js实现带进度条提示的多视频上传功能
2020/12/13 Javascript
JavaScript实现三级级联特效
2017/11/05 Javascript
nodejs超出最大的调用栈错误问题
2017/12/27 NodeJs
Element-UI Table组件上添加列拖拽效果实现方法
2018/04/14 Javascript
[02:34]DOTA2英雄基础教程 幽鬼
2014/01/02 DOTA
Python设计模式之解释器模式原理与用法实例分析
2019/01/10 Python
python读写csv文件方法详细总结
2019/07/05 Python
python输出决策树图形的例子
2019/08/09 Python
Django admin禁用编辑链接和添加删除操作详解
2019/11/15 Python
关于webview适配H5上传照片或者视频文件的方法
2020/11/04 HTML / CSS
德国净水壶和滤芯品牌:波尔德PearlCo(家用净水器)
2020/04/29 全球购物
Linux如何压缩可执行文件
2013/10/21 面试题
优秀校长事迹材料
2014/12/24 职场文书
2015年全国“爱牙日”宣传活动总结
2015/03/23 职场文书
慈善募捐倡议书
2015/04/27 职场文书
先进个人事迹材料(2016推荐版)
2016/03/01 职场文书
Python实现单例模式的5种方法
2021/06/15 Python
关于CSS自定义属性与前端页面的主题切换问题
2022/03/21 HTML / CSS