一、什么是装饰器

装饰器可以让其他函数在不需要做任何代码改变的前提下,增加额外的功能,装饰器的返回值也是一个函数对象。
在 Python 中,函数是第一类对象,也就是说,函数可以做为参数传递给另外一个函数,一个函数可以将另一函数作为返回值,这就是装饰器实现的基础。
装饰器本质上是一个函数,它接受一个函数作为参数。
装饰器的应用场景:插入日志、性能测试、事务处理、缓存等场景。

二、装饰器的形成过程

2.1、不使用装饰器(使用闭包函数)
如果想要测试一个函数的执行时间,在不改变这个函数的前提下可以这样做

import time

def func1():
print('aaa') def timer(func):
def inner():
start_time = time.time()
func()
end_time = time.time()
print('程序用时%s'%(end_time - start_time))
return inner func1 = timer(func1)
func1()

结果:
aaa
程序用时0.0

2.2、不带参数的装饰器
如果有多个函数都需要测试运行时间,就需要分别定义多个func2、func3等等,再调用这样函数,非常麻烦
所以python提供了语法糖,也叫装饰器。

import time

def timer(func):
def inner():
start_time = time.time()
func()
end_time = time.time()
print('程序用时%s'%(end_time - start_time))
return inner @timer #这里相当于执行了func1 = timer(func1)
def func1():
print('aaa') func1()

结果:
aaa
程序用时0.0

func1其实就是执行了timer(func1),返回值其实就是inner,
那么执行func1()其实就是执行了inner(),而inner()函数其实又是执行了传入的函数func1()并打印了一句话

2.3、带参数的装饰器
如果函数需要传入一个参数,那么上面的程序会报错,因为在执行timer()函数时,内部其实是调用了func函数,但是这里并没有给func()函数传入参数,所以会报错。那么尝试给timer()函数也传入一个参数。

import time

def timer(func):
def inner(a): #inner()需要传入参数给里面的子函数func()
start_time = time.time()
func(a) #调用传入的带参数的函数func1()
end_time = time.time()
print('程序用时%s'%(end_time - start_time))
return inner @timer #这里相当于执行了func1 = timer(func1)
def func1(n):
print(n) func1(100)

结果:
100
程序用时0.0

流程:
1)func1其实就是在执行timer(func1),也就是得到了返回值inner
2)func1(100)其实是在执行inner(100)
3)inner(100)其实是在执行func(100)和print语句

2.4、可以接收所有类型的参数的装饰器
上面的例子中只是传入了一个参数,如果传入2个或多个参数,会报错如下:
TypeError: inner() takes 1 positional argument but 2 were given
那么装饰器也需要随着传入多个参数,这显然是不方便的。
那么就需要在装饰器的形参中使用万能参数(*args, **kwargs)来接收所有类型的实参。

import time

def timer(func):
def inner(*args, **kwargs): #这里要求使用万能参数来接收所有类型的多个参数
start_time = time.time()
func(*args, **kwargs) #这里也是要使用万能参数
end_time = time.time()
print('程序用时%s'%(end_time - start_time))
return inner @timer #这里相当于执行了func1 = timer(func1)
def func1(x,y):
print(x,y) func1(100,200)

结果:
100 200
程序用时0.0

2.5、有返回值的参数器
如果被装饰函数有return返回值,那么装饰器内部也需要将被装饰函数的值返回

def timer(func):
def inner(*args, **kwargs):
执行代码前要做的
ret = func(*args, **kwargs)
执行代码后要做的
return ret
return inner

例子:

import time
def timer(func):
def inner(*args, **kwargs): #这里要求使用万能参数来接收所有类型的多个参数
start_time = time.time()
ret = func(*args, **kwargs) #这里也是要使用万能参数
end_time = time.time()
print('程序用时%s'%(end_time - start_time))
return ret
return inner @timer
def func1(x,y):
return x+y
print(func1(100,200))

结果:
程序用时0.0
300

2.6、装饰器查看函数信息
如果被装饰的函数中存在函数说明文档时

def func1(x,y):
'''
这是func1的说明文档
'''
print(x,y) #func1(100,200)
print(func1.__doc__) #查看函数注释的方法
print(func1.__name__) #查看函数名的方法

结果:
这是func1的说明文档
func1

但是当函数加上装饰器后则不能正常显示被装饰函数的注释和函数名,而是显示了装饰器的注释和方法
None
inner

解决方法:使用warps

import time
from functools import wraps def timer(func):
@wraps(func) #加在最内层函数的正上方
def inner(*args, **kwargs):
start_time = time.time()
ret = func(*args, **kwargs)
end_time = time.time()
print('程序用时%s'%(end_time - start_time))
return ret
return inner @timer #这里相当于执行了func1 = timer(func1)
def func1(x,y):
'''
这是func1的说明文档
'''
print(x,y) #func1(100,200)
print(func1.__doc__)
print(func1.__name__)

结果:
这是func1的说明文档
func1

三、 开放封闭原则

对扩展是开放的
对于任何一个程序,在设计之初是可能将所有的功能都一次性设计好,后续一般都会有功能的添加、修改等操作,所以就必须要求代码是可
扩展的。

对修改是封闭的
在一个函数被开发出来之后很有可能会被其他程序引用,这个如果修改了这个函数的代码,就很有可能会影响到其他函数或程序。

而装饰器完美的遵循了这个开放封闭原则

四、装饰器的功能和固定结构

4.1、不支持函数说明文档的

def timer(func):
def inner(*args, **kwargs):
执行代码前要做的
ret = func(*args, **kwargs)
执行代码后要做的
return ret
return inner

4.2、支持函数说明文档的

from functools import wraps
def timer(func):
@wraps(func) #加在最内层函数的正上方
def inner(*args, **kwargs):
执行代码前要做的
ret = func(*args, **kwargs)
执行代码后要做的
return ret #return要放在最后,因为return后函数也就退出了
return inner

五、带参数的装饰器

如果有大量的函数使用了同一个装饰器,这时需要将这些函数同时取消掉这个装饰器,那么一个一个取消就太麻烦了。
这时可以在设计装饰器的时候就让装饰器带一个参数,当需要生效或者取消时可以改变被装饰函数上面的装饰器参数。

import time
from functools import wraps #修改装饰器代码,在原装饰器外面再包裹一层函数用来接收flag状态
def outer(flag):
def timer(func):
@wraps(func)
def inner(*args, **kwargs):
if flag: #这里判断flag的bool值,如果为True则执行下面代码,如果为False则不执行,从而使装饰器失效
start_time = time.time()
ret = func(*args, **kwargs)
end_time = time.time()
print('程序用时%s'%(end_time - start_time))
return ret
return inner
return timer #这里加上一句,返回原装饰器函数 @outer(False) #这里装饰器改为新的装饰器并传入参数True或False,来控制装饰器的启用状态
def func1(x,y):
print(x,y) func1(100,200)

结果:当新的装饰器outer()参数为False时则装饰器失效,如果想再次启用该装饰器,只需要批量将outer()参数改为True即可

六、多个装饰器装饰一个函数

当某个函数需要被多个装饰器装饰时,只需要在该函数上面添加多个装饰器即可

def wrapper1(func):
def inner(*args, **kwargs):
执行代码前要做的
ret = func(*args, **kwargs)
执行代码后要做的
return ret
return inner def wrapper2(func):
def inner(*args, **kwargs):
执行代码前要做的
ret = func(*args, **kwargs)
执行代码后要做的
return ret
return inner @wrapper2
@wrapper1
def func():
print('')
func()

这里func()函数的状态是先被wrapper1装饰,再被wrapper2装饰。

例子:

def wrapper1(func):
def inner(*args, **kwargs):
print('wrapper1,before func')
ret = func(*args ,**kwargs)
print('wrapper1,after func')
return ret
return inner def wrapper2(func):
def inner(*args, **kwargs):
print('wrapper2,before func')
ret = func(*args, **kwargs)
print('wrapper2,after func')
return ret
return inner
@wrapper2 #f = wrapper2(f),里面的f是inner1,外面的f是inner2
@wrapper1 #f = wrapper2(f),里面的f是函数名f,外面的f是inner1
def f():
print('haha')

结果:
wrapper2,before func
wrapper1,before func
haha
wrapper1,after func
wrapper2,after func

七、例子

例子1、记录函数的调用日志:

from datetime import datetime
def log(func):
def decorator(*args, **kwargs):
print('Function ' + func.__name__ + ' has been called at ' + datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
ret = func(*args, **kwargs)
return ret
return decorator
@log
def add(x, y):
return x + y
print(add(1, 2))

结果:
Function add has been called at 2018-11-01 18:01:34
3

day17-函数装饰器的更多相关文章

  1. Python高手之路【四】python函数装饰器

    def outer(func): def inner(): print('hello') print('hello') print('hello') r = func() print('end') p ...

  2. Python: 无参数的函数装饰器

    写带参数的函数装饰器最纠结的是需要包好多层,最外层是接收参数的函数,它返回一个接收函数的的函数.但这样有个问题是,最终包装出来的装饰器必须加()调用一下,即使没有参数也需要这样做,因为调用这个最外层函 ...

  3. Python中利用函数装饰器实现备忘功能

    Python中利用函数装饰器实现备忘功能 这篇文章主要介绍了Python中利用函数装饰器实现备忘功能,同时还降到了利用装饰器来检查函数的递归.确保参数传递的正确,需要的朋友可以参考下   " ...

  4. 【Python 函数对象 命名空间与作用域 闭包函数 装饰器 迭代器 内置函数】

    一.函数对象 函数(Function)作为程序语言中不可或缺的一部分,但函数作为第一类对象(First-Class Object)却是 Python 函数的一大特性. 那到底什么是第一类对象(Firs ...

  5. python 函数 装饰器 内置函数

    函数 装饰器 内置函数 一.命名空间和作用域 二.装饰器 1.无参数 2.函数有参数 3.函数动态参数 4.装饰器参数 三.内置函数 salaries={ 'egon':3000, 'alex':10 ...

  6. python基础—函数装饰器

    python基础-函数装饰器 1.什么是装饰器 装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能. 装饰器的返回值是也是一个函数对象. 装饰器经常用于有切 ...

  7. Python档案袋(函数与函数装饰器 )

    特点:代码复用.可扩展.保持一致性 函数简单的实现,返回值的不同: #定义方法 def funx1(): pass def funx2(): return 0 def funx3(): return ...

  8. C++函数装饰器

    今天在网上看到一个python实现的函数装饰器,尝试用C++11实现了一下,最后很粗糙的完成了,代码如下. 函数装饰器:接受一个函数.将此函数进行一些装饰,成为另一个函数.新生产的函数具有原函数的功能 ...

  9. python 修改的函数装饰器

    把好的代码记录下来 方便以后学习 修改的函数参数装饰器 from functools import wraps import time import logging def warn(timeout) ...

  10. python装饰器1:函数装饰器详解

    装饰器1:函数装饰器 装饰器2:类装饰器 装饰器3:进阶 先混个眼熟 谁可以作为装饰器(可以将谁编写成装饰器): 函数 方法 实现了__call__的可调用类 装饰器可以去装饰谁(谁可以被装饰): 函 ...

随机推荐

  1. PAT 乙级 1051 复数乘法 (15) C++版

    1051. 复数乘法 (15) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 CHEN, Yue 复数可以写成(A + Bi)的常规 ...

  2. react路由传值

    在上一篇总结了react中路由的基本用法,实现了基本的页面跳转,但这肯定是不够用的,比如说在新闻列表页面,点击某一条新闻,希望页面能跳转到新闻详情页,又该如何实现呢? 首先继续上一篇的项目,添加一个新 ...

  3. 如何修改CSV文件的分隔符

    Excel转成CSV文件,在Windows下默认的列表分隔符是逗号(,).在某些情况下,我们可能需要更改列表分隔符,如某个单元格内容包含逗号,而这可能导致使用它的程序出错,这样就需要更改列表分隔符:再 ...

  4. 在Express中使用Multiparty进行文件上传及POST、GET参数获取

    Express 版本:4.14.1 在Express中,文件上传需要用到multiparty中间件,在项目目录中,通过npm install multiparty –save进行安装必要组件. 前端H ...

  5. [UE4]控制台命令,生成机器人

    在关卡蓝图中: 运行游戏的手,按“·”键(键盘第二行第一个键,数字1前面的一个键)呼出控制台输入界面,输入“ce 事件名称 参数值”,然后回车. 可以添加一个Trigger Box作为机器人的出生点

  6. 小程序API录音 微信录音后 Silk格式转码MP3

    http://www.cnblogs.com/wqh17/p/6911748.html

  7. 使用docker搭建redis主从模式

    前期准备: 本地Linux版本:CentOS Linux release 7.5.1804 (Core)Docker版本:Docker version 1.13.1, build dded712/1. ...

  8. delphi版本对应

    delphi 7 delphi 8delphi 2005 ----- 9delphi 2006 ----- 10 delphi 2007 ----- 11delphi 2009 ----- 12 de ...

  9. (转)OpenSystemArchitect - 根据数据库表逆向生成数据模型

    原文地址:http://www.cnblogs.com/zhaojin/archive/2011/04/14/2016478.html OpenSystemArchitect - 根据数据库表逆向生成 ...

  10. 自媒体运营排版利器----Markdown here

    Markdown Here ​ 下载chrome插件直接下载 使用:打开网页文章编辑器,比如cnblog 用markdown语法写文章,之后点击编译 可以设置好css语法,以后每次可以套用同样的模板 ...