Python装饰器探险
关于python装饰器的理解和用法,推荐廖雪峰老师和这一篇博客以及知乎
以下代码均已手动敲过,看完本篇内容,包你装饰器小成!
装饰器实际上就是为了给某程序增添功能,但该程序已经上线或已经被使用,那么就不能大批量的修改源代码,这样是不科学的也是不现实的,因此就产生了装饰器,使得其满足:
- 不能修改被装饰的函数的源代码
- 不能修改被装饰的函数的调用方式
- 满足1、2的情况下给程序增添功能
首先需要理解的是在Python中一切皆对象,函数也是对象,且函数对象可以赋值给变量,所以通过变量也能调用该函数。
>>> def now():
... return '2018-3-11'
...
>>> f = now
>>> f()
2018-3-11
然后需要理解的是函数后面加个()才是调用,也就是函数被执行了
>>> type(f())
<class 'str'>
>>> type(f)
<class 'function'>
接着简单粗暴直接上例子
如我我们想要给一个函数增加统计代码运行时间的功能,但又不能修改原函数,且不能修改原函数的调用方式,比如下面这样子:
import time def test():
time.sleep(2)
print("test is running!") def deco(func):
start = time.time()
func() #
stop = time.time()
print(stop-start) deco(test) #
执行结果:
test is running!
2.001065731048584
我们来看一下这段代码,在#1处,我们把test当作实参传递给形参func,即func=test。注意,这里传递的是地址,也就是此时func也指向了之前test所定义的那个函数体,可以说在deco()内部,func就是test。在#2处,把函数名后面加上括号,就是对函数的调用(执行它)。
但是这修改了原函数的调用方式(deco(test)算怎么回事?),要求一定要是test()这样的调用方式,所以接着改进,用到了嵌套函数:
import time def timer(func): #
def deco():
start = time.time()
func() #
stop = time.time()
print(stop-start)
return deco def test():
time.sleep(2)
print("test is running!")
test = timer(test) #
test() #
结果如下:
test is running!
2.0003929138183594
首先,在#6处,把test作为参数传递给了timer(),此时,在timer()内部,func = test,接下来,定义了一个deco()函数,当并未调用,只是在内存中保存了,并且标签为deco。在timer()函数的最后返回deco()的地址deco。
然后再把deco赋值给了test,那么此时test已经不是原来的test了,也就是test原来的那些函数体的标签换掉了,换成了deco。那么在#7处调用的实际上是deco()。
那么这段代码在本质上是修改了调用函数,但在表面上并未修改调用方式,而且实现了附加功能。
那么真正的装饰器也就是实现了以上的功能,只是用到了另一种语法功能--语法糖:
import time def timer(func): #
def deco():
start = time.time()
func()
stop = time.time()
print(stop-start)
return deco @timer
def test():
time.sleep(2)
print("test is running!") #test = timer(test) #6
#print(test) test() #
分析:这段代码跟上面的代码相比,其实是把test=timer(test)换成了@timer,所以说@timer的作用就是把test这个被装饰函数当作func参数传到装饰器timer里面,然后遇到了deco函数,先不执行这个函数(因为还没有调用),然后返回deco函数地址,这时候deco这个函数就给了test,接着执行test()的时候,就要执行deco函数了,注意deco函数里的func已经变成了原来那个test了
执行结果:
test is running!
2.000576972961426
接着是被装饰函数带参数的情况:
import time def timer(func):
def wrapper(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs)
stop = time.time()
print(stop-start)
return res
return wrapper @timer #相当于执行了语句test = timer(test)
def test(a,b): #
time.sleep(2)
#print("test is running!")
return a+b
a = test(1,2)
print(a)
实际的被装饰函数可能是有参数的,也可能是有返回值的,那么就应该把装饰器里嵌套的函数也带上参数,但是应该是可变参数*args和**kwargs,方便传给func,注意这里面func的返回值赋值给了res,而wrapper也是有返回值的,所以test(1,2)最后是有一个返回值3
结果如下:
2.0004124641418457
3
接着再看带参数的装饰器:
假如说实际上有这样需求:对不同的函数进行不同功能的装饰,那么就需要知道对哪个函数进行哪个装饰,就需要给装饰器加上参数来辨认函数
import time
import functools def timer(parameter):
def decorator(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
if parameter =='task1':
start = time.time()
res = func(*args,**kwargs)
stop = time.time()
print("the task1 run time is :",stop - start)
return res
elif parameter =='task2':
start = time.time()
res = func(*args,**kwargs)
stop = time.time()
print("the task2 run time is :",stop - start)
return res
return wrapper
return decorator @timer(parameter='test1')
def test1(a,b): #
time.sleep(2)
#print("test is running!")
return a+b @timer(parameter='test2')
def test2(a,b): #
time.sleep(3)
#print("test is running!")
return a+b a = test1(1,2)
b = test2(2,3)
print(a,b,test1.__name__)
代码分析:@timer(parameter='test1')相当于test1 = timer(parameter='test1')(test1),也就是说timer(parameter='test1')返回了decorator,timer(parameter)(test1)是返回了wrapper这个函数,接着wrapper变成了test1,最后调用test1的时候,也就是执行了wrapper
.__name__方法是指查看原函数的名字
注意:这里还涉及到了嵌套函数的知识点:函数只能调用和他同级或比它高级的变量和函数,这里wrapper可以调用func函数,也可以调用parameter参数,就是这个道理
结果如下:
the task1 run time is : 2.000300645828247
the task2 run time is : 3.0010263919830322
3 5 test1
至此已经大功告成,但是第二行和第六行的代码是什么意思呢,原来如果不加这两句的话,被装饰函数的名字就变成了wrapper,不信请看:
import time
import functools def timer(parameter):
def decorator(func):
#@functools.wraps(func)
def wrapper(*args,**kwargs):
if parameter =='test1':
start = time.time()
res = func(*args,**kwargs)
stop = time.time()
print("the task1 run time is :",stop - start)
return res
elif parameter =='test2':
start = time.time()
res = func(*args,**kwargs)
stop = time.time()
print("the task2 run time is :",stop - start)
return res
return wrapper
return decorator @timer(parameter='test1')
def test1(a,b): #
time.sleep(2)
#print("test is running!")
return a+b @timer(parameter='test2')
def test2(a,b): #
time.sleep(3)
#print("test is running!")
return a+b a = test1(1,2)
b = test2(2,3)
print(a,b,test1.__name__)
我把第六行注释掉之后,结果:
the task1 run time is : 2.000321388244629
the task2 run time is : 3.000973701477051
3 5 wrapper
所以为了保持原函数的纯洁,还是要加上这两句代码
至此,装饰器至少已经入门了。
Python装饰器探险的更多相关文章
- 关于python装饰器
关于python装饰器,不是系统的介绍,只是说一下某些问题 1 首先了解变量作用于非常重要 2 其次要了解闭包 def logger(func): def inner(*args, **kwargs) ...
- python装饰器通俗易懂的解释!
1.python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,猜有点点开始明白了.总结了一下解释得比较好的,通俗易懂的来说 ...
- Python 装饰器学习
Python装饰器学习(九步入门) 这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 1 2 3 4 5 6 7 8 # -*- c ...
- python 装饰器修改调整函数参数
简单记录一下利用python装饰器来调整函数的方法.现在有个需求:参数line范围为1-16,要求把9-16的范围转化为1-8,即9对应1,10对应2,...,16对应8. 下面是例子: def fo ...
- python 装饰器学习(decorator)
最近看到有个装饰器的例子,没看懂, #!/usr/bin/python class decorator(object): def __init__(self,f): print "initi ...
- Python装饰器详解
python中的装饰器是一个用得非常多的东西,我们可以把一些特定的方法.通用的方法写成一个个装饰器,这就为调用这些方法提供一个非常大的便利,如此提高我们代码的可读性以及简洁性,以及可扩展性. 在学习p ...
- 关于python装饰器(Decorators)最底层理解的一句话
一个decorator只是一个带有一个函数作为参数并返回一个替换函数的闭包. http://www.xxx.com/html/2016/pythonhexinbiancheng_0718/1044.h ...
- Python装饰器由浅入深
装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码.装饰器不光能装饰函数,也能装饰其他的对象,比如类,但通常,我们 ...
- Python装饰器与面向切面编程
今天来讨论一下装饰器.装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等.装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数 ...
随机推荐
- VBScript.RegExp 正则表达式excel vba 学习经验
1) 手动引用(前期绑定) 点击VBE编辑器菜单:工具 - 引用,选取: Microsoft VBScript Regular Expressions 5.5 Dim regex As New ...
- Swift -- 中文版两大官方文档汇总
Swift官方文档由CocoaChina翻译小组精心翻译制作而成,目前两本文档中文版已全部完成!在此,我们对所有参与的译者.组织人员以及工作人员表示衷心的感谢!本文为您提供两本文档的在线阅读以及下载! ...
- Hadoop2.2.0分布式安装配置详解[2/3]
前言 本文主要通过对hadoop2.2.0集群配置的过程加以梳理,所有的步骤都是通过自己实际测试.文档的结构也是根据自己的实际情况而定,同时也会加入自己在实际过程遇到的问题.搭建环境过程不重要,重要点 ...
- IDEA使用笔记(八)——自动生成 serialVersionUID 的设置
这个设置比较简单,也有一些博文已经写到了,为什么我还要写哪?(潜台词:因为我想凑一篇博文)我觉得学习,特别是编程学习是需要重复造轮子的,另外,就是加深自己的印象方便自己的查找.还有就是关键点,有些博客 ...
- php验证码--图片
这里我们介绍图片验证码的制作,有关字符验证码能够參考下面文章: 点击打开链接 图片验证码的制作分三步: 1.制作图片库 2.随机选取一张图片 3.输出图片内容 代码例如以下(这里为了方便我直接用的本地 ...
- Disruptor学习笔记
前言 以前一直听说有Disruptor这个东西,都说性能很强大,所以这几天自己也看了一下. 下面是自己的学习笔记,另外推荐几篇自己看到写的比较好的博客: Disruptor——一种可替代有界队列完成并 ...
- struts2:表单标签续(datetimepicker标签)
18. datetimepicker标签 18.1 拷贝JAR包 在struts-2.x.x压缩包的lib目录中拷贝struts2-dojo-plugin-2.3.8.jar到WEB-INF/lib目 ...
- JS 日期转换,格式化等常用的函数定义
//判断字符串是否日期格式 function isDate(val) { return new Date(val) != "Invalid Date"; } //日期格式化 fun ...
- pandas DataFrame(1)
之前介绍了numpy的二维数组,但是numpy二维数组有一些局限性,比如,它数组里所有的值的类型必须相同,不能某一列是数值型,某一列是字符串型,这样会导致无法使用 mean() , std() 等方法 ...
- 【Java多线程】JDK1.5并发包API杂谈
并发与并行 并发 一个或多个处理器执行更多的任务(通过划分时间片来执行更多的任务),从逻辑上实现同时运行: 如,N个并发请求在一个两核CPU上: 并行 N个处理器分别同时执行N个任务,从物理上实现同时 ...