一、什么是装饰器

装饰器可以让其他函数在不需要做任何代码改变的前提下,增加额外的功能,装饰器的返回值也是一个函数对象。
在 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. js面向对象1

    1.在空白的Object上加属性和方法: <script type="text/javascript"> function createPerson(name, qq) ...

  2. sudo 命令报错的解决方法

    尝试着用终端打开Mac的安全权限(sudo spctl --master-disable),却显示以下提示,望高手解答. sudo: /etc/sudoers is world writablesud ...

  3. hadoop长时间运行后,stop-all.sh报错

    报错现象: hadoop在stop-all.sh的时候依据的是datanode上的mapred和dfs进程号. 而默认的进程号保存在/tmp下,linux默认会每 隔一段时间(一般是一个月或者7天左右 ...

  4. Scala集合类型详解

    Scala集合 Scala提供了一套很好的集合实现,提供了一些集合类型的抽象. Scala 集合分为可变的和不可变的集合. 可变集合可以在适当的地方被更新或扩展.这意味着你可以修改,添加,移除一个集合 ...

  5. redis实现对账(集合比较)功能

    现状:每日在进行系统之间的订单对账时,往往是这样的操作流程: 1.从外部系统拉取数据存入本地数据库: 2.查询本地订单数据集合localSet: 3.查询外部系统订单数据集合outerSet; 4.以 ...

  6. JVM底层又是如何实现synchronized的【转载】

    目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug Lea.本文并不比较synchronized与Loc ...

  7. PHP程序员的进阶之路

    第1阶段:初级PHP程序员 重点:把LNMP搞熟练(核心是安装配置基本操作)目标:能够完成基本的LNMP环境安装,简单配置维护:能够做基本的简单系统的PHP开发:能够在PHP中型系统中支持某个PHP功 ...

  8. kickstart

    关闭防火墙.关闭selinux 1.配置DHCP服务 # yum install dhcp -y dhcp配置文件如下 # vi /etc/dhcp/dhcpd.conf 查看路径 # rpm -ql ...

  9. android 根据滑动隐藏或显示导航 类似手机QQ好友个人信息

    //重写ScrollView public class NotifyingScrollView extends ScrollView { /** * @author Cyril Mottier */ ...

  10. ueditor 正在读取目录及网络链接错误

    环境 ueditor1_3_5-gbk-net .NET版本3.5 如果把项目直接改成4.0不会出现这样的问题,查看 问题1:正在读取目录 找到ueditor/ueditor.config.js  找 ...