装饰器

一、装饰器的本质

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

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

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

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

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

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

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

1.高阶函数

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

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

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

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

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

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

#举例:
def name1():
print('from name1')
def name2():
print('from name2')
return name1
n = name2()
n() #执行结果
from name2
from name1
#程序分析:
#函数name2()中的返回值中包含函数名name1,所以函数name2()为高阶函数
#将函数名name1赋值给变量n,函数名为该函数内存地址
#n()为执行函数name1

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

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

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

2.函数嵌套

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

#举例:

def first():
print('from first ')
def second():
print('from second')
def third():
print('from third')
third()
second()
first() #执行结果
from first
from second
from third

#程序分析

3.闭包

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

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

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

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

闭包的基本形式是:

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

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

#举例理解:
x = 1000
def f1():
x = 1
def f2():
print(x)
return f2
f = f1()
print(f)
f()
x = 123 #执行结果
<function f1.<locals>.f2 at 0x003394B0>
1

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

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

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

三、装饰器的框架

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

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

def timmer(func)
def wrapper():
func()
return wrapper

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

import time
def timmer(func):
def wrapper():
start_time = time.time()
func()
stop_time = time.time()
print('程序运行时间是%s' % (stop_time - start_time))
return wrapper
def test():
time.sleep(3)
print('test程序运行完毕')
test = timmer(test)
test() #执行结果
test程序运行完毕
程序运行时间是3.000171661376953

#程序分析

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

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

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

import time
def timmer(func):
def wrapper():
start_time = time.time()
func()
stop_time = time.time()
print('程序运行时间是%s' % (stop_time - start_time))
return wrapper
@timmer
def test():
time.sleep(3)
print('test程序运行完毕')
test()

四、带返回值的装饰器

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

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

#回顾:

  • 参数组(非固定长度的参数)
  • *args 针对位置参数传递
  • **kwargs 针对关键字参数传递
  • *表传递的数据类型为列表
  • **表传递的数据类型为字典
  • 位置参数,必须一一对应,缺一不行,多一也不行
  • 关键字参数,无需一一对应,缺一不行,多一也不行
  • 关键字参数和位置参数混合使用时,位置参数必须在关键字参数左边
#将上述程序进行修改
import time
def timmer(func):
def wrapper(*args,**kwargw):
start_time = time.time()
func(*args,**kwargw)
stop_time = time.time()
print('程序运行时间是%s' % (stop_time - start_time))
return wrapper
@timmer
def test1(name,age):
time.sleep(3)
print('test程序运行完毕,名字是%s,年龄是%s' %(name,age))
@timmer
def test2(name,age,gender):
time.sleep(3)
print('test程序运行完毕,名字是%s,年龄是%s,性别是%s' %(name,age,gender))
test1('xhg',age = 18)
test2('xhg', 18, gender = 'male') #执行结果
test程序运行完毕,名字是xhg,年龄是18
程序运行时间是3.000171422958374
test程序运行完毕,名字是xhg,年龄是18,性别是male
程序运行时间是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. Cisco interview

    A.  1. Self-introduction I am Yanlin He . I am a master degree candidate of school of infomation sci ...

  2. 2019.01.21 bzoj2989: 数列(二进制分组+主席树)

    传送门 二进制分组入门题. 主席树写错调题2h+2h+2h+体验极差. 题意简述:给一堆点,支持加入一个点,询问有多少个点跟(x,y)(x,y)(x,y)曼哈顿距离不超过kkk. 思路:题目要求的是对 ...

  3. 2019.01.21 bzoj2441: [中山市选2011]小W的问题(树状数组+权值线段树)

    传送门 数据结构优化计数菜题. 题意简述:给nnn个点问有多少个www型. www型的定义: 由5个不同的点组成,满足x1<x2<x3<x4<x5,x3>x1>x2 ...

  4. vue-cli引入mui的步骤

    不用npm安装了 1.mui官方GitHub下载mui所需文件 https://github.com/dcloudio/mui 把下载来的dist文件夹整个复制到static文件夹中 2.在index ...

  5. mysql (_mysql_exceptions.OperationalError) (1055, "Expression #1 of SELECT list is not in GROUP BY clause

    sudo gedit /etc/mysql/my.cnf在打开的my.cnf文件中添加 sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES 保存,退 ...

  6. IntelliJ IDEA 2017版 spring-boot 2.0.3 邮件发送搭建,概念梳理 (二)

    第二部分 邮件发送历史   一.第一封邮件   1.1969年10月,世界上的第一封电子邮件    1969年10月世界上的第一封电子邮件是由计算机科学家Leonard K.教授发给他的同事的一条简短 ...

  7. IntelliJ IDEA 2017版 spring-boot2.0.4+mybatis+Redis处理高并发,穿透问题

    一.当采用reddis缓存的时候,如果同时,一万次访问,那么就会有10000次访问数据库所以就会对数据库造成巨大压力,这时候,就要用到线程 1.方法体上加锁(优点,防护住了并发锁,缺点降低了内存效率) ...

  8. POJ 3388 Japanese Puzzle (二分)

    题意:给你一个n*n 的图,你总共有k 种花砖,告诉你每一种花砖的个数,让你随便安排它们的位置,问你最多有多少行和第一行是一样,并且要输出第一行的一定存在的图案. 析:首先这个题如果读懂了题意,一点也 ...

  9. new命令简化的内部流程

    构造函数返回对象的一些问题: function fn(name,age){ this.name = name; this.age = age; //return 23; 忽略数字,直接返回原有对象 / ...

  10. C++获取当前进程绝对路径

    获取进程的绝对路径(代码同时操作字符串获取了文件目录): 第一种代码: wstring GetProgramDir() { TCHAR exeFullPath[MAX_PATH]; // Full p ...