装饰器的应用背景
上一篇博客中总结了闭包函数 ,装饰器就是闭包函数的应用之一。 那什么是装饰器呢? 装饰器产生于什么样的背景下呢?
在实际的开发过程中,由于需求分析不准确或者需求的不断变化,往往会出现在原函数功能的基础上增加新的功能,但不能影响原函数的调用方式。该怎么办呢?
你可能会说:“那直接修改函数啊!”。 万万不可!!! 直接修改原函数内容是十分危险的方法,工程项目内容庞大,牵一发而动全身,搞不好会出现你无法处理的bug。
那么更安全的办法就是使用装饰器函数。
开放封闭原则
1.对拓展是开放的
在设计一个程序之初,我们很难考虑到所有任务及功能并保证未来不对其做任何修改。所以我们允许在后期对程序做一下扩展,添加新功能。
2.对修改是封闭的
在对程序修改之前,我们可以已经将程序交付给用户或者其他开发人员了,这时对其修改,会影响到其他人的使用,且容易出现意想不到的bug。
装饰器恰恰遵循了开放封闭原则。
装饰器的形成过程
首先我们有了一个极为朴素的原函数。
def func():
print('我就是帅气的原函数,来装饰我呀!')
func()
我们在原函数的输出语句前后分别加上一句输出,于是我们就有了一个原始版本的装饰器:
def func():
print('我就是帅气的原函数,来装饰我呀!')
def wrapper(fun):
def inner():
print('耶!我在原函数前面')
fun()
print('哦!我在原函数后面')
return inner
func = wrapper(func) # 做一次调用,得到内部函数,即 func = inner
func()
"""
耶!我在原函数前面
我就是帅气的原函数,来装饰我呀!
哦!我在原函数后面
"""
好!我们使用了一个闭包函数来装饰了原函数,顺利完成了任务。但是在每一次调用func()
的时候,还要对func
进行一次重新赋值,让人觉得很碍眼。万一我们在调用的时候忘记了呢?
Python的设计者们也考虑到了这个问题,使用者可以用一句语法糖来解决这个问题。
def wrapper(fun):
def inner():
print('耶!我在原函数前面')
fun()
print('哦!我在原函数后面')
return inner
@wrapper # 这里就是语法糖 ==> func = wrapper(func)
def func():
print('我就是帅气的原函数,来装饰我呀!')
func()
到这里我们装饰原函数的任务完成了吗? 不,还没有! 函数名.__name__
和函数名.__doc__
分别是查看函数名和函数注释的方法。如下:
def func():
"""这里是注释"""
print('我就是帅气的原函数,来装饰我呀!')
print(func.__name__) # func
print(func.__doc__) # 这里是注释
可是被装饰过的原函数使用时,就失效了。
def wrapper(fun):
def inner():
print('耶!我在原函数前面')
fun()
print('哦!我在原函数后面')
return inner
@wrapper
def func():
"""这里是注释"""
print('我就是帅气的原函数,来装饰我呀!')
print(func.__name__) # inner
print(func.__doc__) # None
这是因为原函数被一个闭包函数装饰后,我们在调用原函数时,实际上是在调用闭包函数的内部函数,也就是inner
函数。
我们可以调用functools
模块中的 wraps() 函数。wraps() 本身也是一个装饰器函数,接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。
from functools import wraps
def wrapper(fun):
@wraps(fun)
def inner():
print('耶!我在原函数前面')
fun()
print('哦!我在原函数后面')
return inner
@wrapper
def func():
"""这里是注释"""
print('我就是帅气的原函数,来装饰我呀!')
print(func.__name__) # func
print(func.__doc__) # 这里是注释
装饰器的万能模板
装饰器总结
本质: 一个闭包函数
主要功能: 在不改变原函数调用方式的基础上在原函数的前、后添加功能。
万能模板
在实际的开发过程中,我们遇到的原函数是千差万别的。 有带返回值的,又不带返回值的; 有带参数的,不带参数,或者带多个参数的;也会出现一个装饰器装饰多个函数的情况,而这几个函数的参数个数又都是不同的。面对这些不同的情况,可以使用下面的固定格式,轻松hold住:
def wrapper(fun):
@wraps(fun)
def inner(*args, **kwargs):
print('加在函数前面的功能')
ret = fun(*args, **kwargs)
print('加在函数后面的功能')
return ret
return inner
如何快速取消装饰器
我们辛辛苦苦为10000个函数加上了同一个装饰器,可是这时需求又改了,装饰器的功能不需要了。 怎么办? 难道手动删除一万个@wrapper
吗?还是把把装饰器函数里面的内容修改了? 万一,等下产品经理抽风,又要加上装饰器的功能呢?
这里我们可以给装饰器传递一个参数作为装饰器功能是否使用的标志位。
from functools import wraps
Flag = False # 直接在这里修改Flag标志的值,就可以控制装饰器提供的功能是否启用
def outer(flag):
def wrapper(fun):
@wraps(fun)
def inner(*args, **kwargs):
if flag:
print('加在函数前面的功能')
ret = fun(*args, **kwargs)
if flag:
print('加在函数后面的功能')
return ret
return inner
return wrapper
@outer(Flag) # @out(Flag) ==> @wrapper ==> func = wrapper(func)
def func():
print('我就是原函数')
func()
一个函数被多个装饰器装饰
def wrapper1(fun):
def inner1(*args, **kwargs):
print('wrapper1, before func')
fun(*args, **kwargs)
print('wrapper1, after func')
return inner1
def wrapper2(fun):
def inner2(*args, **kwargs):
print('wrapper2, before func')
fun(*args, **kwargs)
print('wrapper2, after func')
return inner2
@wrapper1 # inner2 = wrapper1(inner2) = inner1
@wrapper2 # func = wrapper2(func) = inner2
def func():
print('我是原函数')
func()
'''运行结果:
wrapper1, before func
wrapper2, before func
我是原函数
wrapper2, after func
wrapper1, after func
'''
多层装饰的效果是嵌套的。