装饰器

一、装饰器的本质

装饰器的本质就是函数,功能就是为其他函数添加附加功能。

利用装饰器给其他函数添加附加功能时的原则:

1.不能修改被修饰函数的源代码
        2.不能修改被修饰函数的调用方式

举例:计算以下一段程序执行时间

  1. #程序:计算0—19的和
  2. def cal(l):
  3. res = 0
  4. for i in l:
  5. res += i
  6. return res
  7. print(cal(range(20)))
  1. #给上述程度增加时间模块
  2. import time
  3. def cal(l):
  4. start_time = time.time()
  5. res = 0
  6. for i in l:
  7. time.sleep(0.1)
  8. res += i
  9. stop_time = time.time()
  10. print('函数的运行时间是%s'%(stop_time - start_time))
  11. return res
  12. print(cal(range(20)))
  13.  
  14. #执行结果
  15. 函数的运行时间是2.0001144409179688
  16. 190
  1. #上述增加的时间模块
  2. 违反了开放封闭原则,改变了cal()函数的源代码,不是实现装饰器的功能。
  3. 因此可以把统计时间的函数单独写出来。并且满足以上两个原则

二、怎么样实现一个基本的装饰器(装饰器的知识储备)

装饰器 = 高阶函数 + 函数嵌套 + 闭包

1.高阶函数

  • 函数接受的参数是一个函数名
  • 函数的返回值是一个函数名
  • 满足上述条件的任何一个都可以是高阶函数

高阶函数类型一:函数接受的参数是一个函数名

  1. #举例:
  2. def name1(n):
  3. print(n)
  4. n('xhg')
  5. def name2(name):
  6. print('my name is %s' %name)
  7. name1(name2)
  8. #执行结果
  9. <function name2 at 0x00E194F8>
  10. my name is xhg
  1. #name1()是一个高阶函数,其接受的参数是函数名name2
  2. #程序分析
  3. #函数名name2是函数name2的内存地址。传给函数name1,即参数n=函数name2的内存地址,因此打印的结果为函数name2的内存地址
  4. #n('xhg'),只执行name2('xhg')

利用‘函数接受的参数是一个函数名’这个思想,为函数name2()添加一个统计时间的功能

  1. import time
  2. def name1(n):
  3. start_time = time.time()
  4. n('xhg')
  5. stop_time = time.time()
  6. print('函数的运行时间是%s'%(stop_time - start_time))
  7. def name2(name):
  8. time.sleep(2)
  9. print('my name is %s' %name)
  10. name1(name2)
  11.  
  12. #执行结果
  13. my name is xhg
  14. 函数的运行时间是2.0001144409179688
  1. #上述程序虽然实现了增加时间模块功能,虽然不改变函数name2()的代码,但是改变了函数name()的调用方式
  2. #因此函数name1()不是装饰器

高阶函数类型二:函数的返回值是一个函数名

  1. #举例:
  2. def name1():
  3. print('from name1')
  4. def name2():
  5. print('from name2')
  6. return name1
  7. n = name2()
  8. n()
  9.  
  10. #执行结果
  11. from name2
  12. from name1
  1. #程序分析:
  2. #函数name2()中的返回值中包含函数名name1,所以函数name2()为高阶函数
  3. #将函数名name1赋值给变量n,函数名为该函数内存地址
  4. #n()为执行函数name1

利用‘函数的返回值是一个函数名’这个思想,为函数name2()添加一个统计时间的功能

  1. import time
  2. def name1(n):
  3. start_time = time.time()
  4. n('xhg')
  5. stop_time = time.time()
  6. print('函数的运行时间是%s' % (stop_time - start_time))
  7. return n
  8. def name2(name):
  9. time.sleep(2)
  10. print('my name is %s' %name)
  11. name2 = name1(name2)
  12. name2('xhg')
  13.  
  14. #执行结果
  15. my name is xhg
  16. 函数的运行时间是2.0001144409179688
  17. my name is xhg
  1. #程序分析:计算函数name2()运行时间的函数模块name1,没有函数name2()改变调用方式,也没有改变数name2()的源代码。
  2. #但是多执行了一次,该装饰器设计不合格

#结论:单独利用高阶函数无法实现装饰器的功能

2.函数嵌套

函数嵌套实际上就是在函数中又定义了一个函数

  1. #举例:
  2.  
  3. def first():
  4. print('from first ')
  5. def second():
  6. print('from second')
  7. def third():
  8. print('from third')
  9. third()
  10. second()
  11. first()
  12.  
  13. #执行结果
  14. from first
  15. from second
  16. from third

#程序分析

3.闭包

关于闭包这块,我没有太理解了。以下的知识摘自这个博文,对理解闭包挺有帮助的。

https://www.cnblogs.com/guobaoyuan/articles/6756763.html

闭包:首先必须是内部定义的函数,该函数包含对外部作用域而不是全局作用域名字的引用

定义:内部函数的代码包含对外部函数的代码的引用,但一定不是对全局作用域的引用

闭包的基本形式是:

在函数F1中,定义F2,F2只能引用F1定义的变量,之后F1函数返回F2的函数名字

这样就保证了可以将F1的执行结果赋予给一个变量,该变量可以在之后的任何时刻随时可以运行

  1. #举例理解:
  2. x = 1000
  3. def f1():
  4. x = 1
  5. def f2():
  6. print(x)
  7. return f2
  8. f = f1()
  9. print(f)
  10. f()
  11. x = 123
  12.  
  13. #执行结果
  14. <function f1.<locals>.f2 at 0x003394B0>
  15. 1

#顺便提一句,要想充分理解这边程序执行的情况,需要对作用域以及变量那边有清楚的认识

使用闭包的好处:自带状态即变量,可以不用传参就用,方便。

  • 闭包(closure)是函数式编程的重要的语法结构。
  • 不同的语言实现闭包的方式不同。
  • Python以函数对象为基础,为闭包这一语法结构提供支持的
  • (我们在特殊方法与多范式中,已经多次看到Python使用对象来实现一些特殊的语法)。
  • Python一切皆对象,函数这一语法结构也是一个对象。
  • 在函数对象中,我们像使用一个普通对象一样使用函数对象,比如更改函数对象的名字,或者将函数对象作为参数进行传递。

三、装饰器的框架

有了以上三个知识为基础铺垫,我们可以搭出来一个装饰器的框架模型

以计算程序执行时间的功能函数为例

  1. def timmer(func)
  2. def wrapper():
  3. func()
  4. return wrapper

根据上述框架来写一个计算程序执行时间的功能的装饰器

  1. import time
  2. def timmer(func):
  3. def wrapper():
  4. start_time = time.time()
  5. func()
  6. stop_time = time.time()
  7. print('程序运行时间是%s' % (stop_time - start_time))
  8. return wrapper
  9. def test():
  10. time.sleep(3)
  11. print('test程序运行完毕')
  12. test = timmer(test)
  13. test()
  14.  
  15. #执行结果
  16. test程序运行完毕
  17. 程序运行时间是3.000171661376953

#程序分析

  1. #timmer(test) 将test()函数的函数名传给timmer函数,实际上是传递的是test的地址
  2. #执行timmer函数的结果是得到wrapper的地址,即test=wrapper的地址
  3. #test()实际上是在执行wrapper函数

注意:@timmer <===> test = timmer(test)

所以,完美的装饰器诞生啦!!!

  1. import time
  2. def timmer(func):
  3. def wrapper():
  4. start_time = time.time()
  5. func()
  6. stop_time = time.time()
  7. print('程序运行时间是%s' % (stop_time - start_time))
  8. return wrapper
  9. @timmer
  10. def test():
  11. time.sleep(3)
  12. print('test程序运行完毕')
  13. test()

四、带返回值的装饰器

  1. #对上面写的这个程序进行一个小小的加工,使得该装饰器有返回值
  2. import time
  3. def timmer(func):
  4. def wrapper():
  5. start_time = time.time()
  6. res = func()
  7. stop_time = time.time()
  8. print('程序运行时间是%s' % (stop_time - start_time))
  9. return res
  10. return wrapper
  11. @timmer
  12. def test():
  13. time.sleep(3)
  14. print('test程序运行完毕')
  15. return '这是test函数的返回值'
  16. res = test()
  17. print(res)
  18.  
  19. #执行结果
  20. test程序运行完毕
  21. 程序运行时间是3.0001718997955322
  22. 这是test函数的返回值
  1. #程序理解
  2. #该程序的理解重点还是要清楚 test() 这一步执行的是哪个函数,分析同上,实际执行 wrapper() 函数
  3. #res = func() 实际执行test()函数,并将test函数的返回值赋值给res变量
  4. #wrapper函数执行完,将局部变量res的值通过return返回给全局变量res

五、带可变长度参数的装饰器

#回顾:

  • 参数组(非固定长度的参数)
  • *args 针对位置参数传递
  • **kwargs 针对关键字参数传递
  • *表传递的数据类型为列表
  • **表传递的数据类型为字典
  • 位置参数,必须一一对应,缺一不行,多一也不行
  • 关键字参数,无需一一对应,缺一不行,多一也不行
  • 关键字参数和位置参数混合使用时,位置参数必须在关键字参数左边
  1. #将上述程序进行修改
  2. import time
  3. def timmer(func):
  4. def wrapper(*args,**kwargw):
  5. start_time = time.time()
  6. func(*args,**kwargw)
  7. stop_time = time.time()
  8. print('程序运行时间是%s' % (stop_time - start_time))
  9. return wrapper
  10. @timmer
  11. def test1(name,age):
  12. time.sleep(3)
  13. print('test程序运行完毕,名字是%s,年龄是%s' %(name,age))
  14. @timmer
  15. def test2(name,age,gender):
  16. time.sleep(3)
  17. print('test程序运行完毕,名字是%s,年龄是%s,性别是%s' %(name,age,gender))
  18. test1('xhg',age = 18)
  19. test2('xhg', 18, gender = 'male')
  20.  
  21. #执行结果
  22. test程序运行完毕,名字是xhg,年龄是18
  23. 程序运行时间是3.000171422958374
  24. test程序运行完毕,名字是xhg,年龄是18,性别是male
  25. 程序运行时间是3.000171422958374

#写在后面

时间很快,2018年很快结束

研究生的日子,过得那么单调乏味

感觉还是本科那会逍遥自在

我是一个很少追剧 追星 追综艺的人

感觉不像这个时代的

但奇葩说是我一直坚持看的

不管别人怎么评价这个节目  反正我很喜欢

他会让我思考  让我从不同的角度去看待这个世界

在辩论死忙时间这一期节目

我觉得特别精彩

我泪点好像有点低  也可能有些话确实触及到我内心深处

虫仔生病的那段经历讲述  让我看到了每一个人的不容易

每一个成年人都有自己的秘密  都生活的那么不容易 每个人都在扛着  没有放弃  每个人也都扛着很好

每一个辩手都有自己的故事

每一个普通人也有自己的故事

关键是 看你以什么样的心态去看待

我特别喜欢黄执中,他说,美好的事物,不是没有裂痕,而是满是裂痕,却没有崩开

通过别人的故事,来更加清楚豁达看待自己的生活

加油,小伙郭

加油,每一个在努力的人

当你觉得很累的时候,躺下来好好休息一下。因为一切都会好起来的!努力的人,运气都不会太差

Python小白学习之路(二十四)—【装饰器】的更多相关文章

  1. Python小白学习之路(十四)—【作用域】【匿名函数】【编程方法论】【高阶函数】

    吧啦吧啦内心戏 在没有具体学作用域之前,我在之前的学习笔记中就有提到 我开始以为是自己自创的词儿 没想到这个词早已经存在(手动捂脸) 真是个无知的小火锅(不知者无罪) 我发现自己最擅长做的事情,就是给 ...

  2. Python小白学习之路(十六)—【内置函数一】

    将68个内置函数按照其功能分为了10类,分别是: 数学运算(7个) abs()   divmod()  max()  min()  pow()  round()  sum() 类型转换(24个) bo ...

  3. Python小白学习之路(十二)—【前向引用】【风湿理论】

    前向引用 风湿理论(函数即变量) 理论总是很抽象,我个人理解: 代码从上到下执行,一旦遇到定义的函数体,内存便为其开辟空间,并用该函数的名字作为一个标识但是该函数体内具体是什么内容,这个时候并不着急去 ...

  4. Python小白学习之路(十)—【函数】【函数返回值】【函数参数】

    写在前面: 昨天早睡之后,感觉今天已经恢复了百分之八十的样子 又是活力满满的小伙郭 今日份鸡汤: 我始终相信,在这个世界上,一定有另一个自己,在做着我不敢做的事,在过着我想过的生活.-------宫崎 ...

  5. Python小白学习之路(十八)—【内置函数三】

    一.对象操作 help() 功能:返回目标对象的帮助信息 举例: print(help(input)) #执行结果 Help on built-in function input in module ...

  6. Python小白学习之路(十五)—【map()函数】【filter()函数】【reduce()函数】

    一.map()函数 map()是 Python 内置的高阶函数 有两个参数,第一个是接收一个函数 f(匿名函数或者自定义函数都OK啦):第二个参数是一个 可迭代对象 功能是通过把函数 f 依次作用在 ...

  7. Python小白学习之路(十九)—【文件操作步骤】【文件操作模式】

    一.文件操作步骤 step1:打开文件,得到文件句柄并赋值给一个变量step2:通过句柄对文件进行操作step3:关闭文件 举例: a = open('hello world', 'r', encod ...

  8. 嵌入式Linux驱动学习之路(二十四)Nor Flash驱动程序

    Nor Flash和Nand Flash的不同: 类型 NOR Flash  Nand Flash  接口 RAM-like,引脚多 引脚少 容量 小(1M.2M...) 大(512M.1G) 读 简 ...

  9. IOS学习之路二十四(UIImageView 加载gif图片)

    UIImageView 怎样加载一个gif图片我还不知道(会的大神请指教),不过可以通过加载不同的图片实现gif效果 代码如下: UIImageView* animatedImageView = [[ ...

  10. IOS学习之路二十四(custom 漂亮的UIColor)

    下面简单列举一下漂亮的和颜色,大家也可以自己依次试一试选出自己喜欢的. 转载请注明 本文转自:http://blog.csdn.net/wildcatlele/article/details/1235 ...

随机推荐

  1. 使用thymeleaf一旦没有闭合标签就会报错怎么解决

    问题:input标签未关闭报bug,代码稍有不慎就出小问题 使用springboot的thymeleaf模板时默认会对HTML进行严格的检查,导致当你的标签没有闭合时就会通不过,例如: //要想通过, ...

  2. R入门(一)

    简单的算术操作和向量运算 向量赋值:函数c( ),参数可以是一个或多个数,也可以是向量 赋值符号‘<-’ 向量运算:exp(),log(),sin(),tan(),sqrt(),max(),mi ...

  3. 指令发布中如何实现new新消息的提醒?

    设计思路:反馈后,最急需了解反馈结果的是申请人,故给每一条反馈信息添加一个查看状态的字段,如CK_STATUS,并为这个状态设计为char(1)类型,java bean中使用integer可以实现默认 ...

  4. ES6展开运算符的3个用法

    展开运算符的用法1:传参 // 展开运算符的用法1 : 传参 function test(a,b) { return a + b ; } var arr = [1,2]; console.log(te ...

  5. C[a,b]向量空间中的函数的线性相关性

  6. 构造函数的prototype和constructor属性

    Car.prototype = { name:'BMW', height:1400, long:4900 } function Car(color,owner){ this.color = color ...

  7. PHP上传文件参考配置大文件上传

    PHP用超级全局变量数组$_FILES来记录文件上传相关信息的. 1.file_uploads=on/off 是否允许通过http方式上传文件 2.max_execution_time=30 允许脚本 ...

  8. C++对象模型:成员变量<一>非静态成员变量

    非静态成员变量,分别两种可能,要么类自定义,要么继承而来.根据<深度探索C++对象模型>的解读. class X { private: int x,y,z; }; 在这个类中,有三个私有成 ...

  9. (转)设置VMWARE通过桥接方式使用主机无线网卡上网

    转自:http://www.cnblogs.com/liongis/p/3265458.html 环境:WIN7旗舰版,台式机,U盘无线上网卡. 虚拟软件:VMware9.0,虚拟系统:CentOS6 ...

  10. 一天学习两个设计模式之Facade模式(外观模式,结构型模式)

    程序这东西随着时间推移,程序会越来越大,程序中的类越来越多,而且他们之间相互关联,这会导致程序结构变得越来越复杂.因此我们在使用他们时候,必须要弄清楚他们之间的关系才能使用他们. 特别是在调用大型程序 ...