我们在开发一个项目的时候,需要尽量遵循这样一个规则:修改是封闭的,扩展是开放的;也就是说在后期更改需求的时候,我们可以不改动以前的代码,而只需要通过扩展就可以满足新需求。Python的装饰器就是帮助我们来实现这一个目标的方法之一。
一、什么是装饰器
通过一个示例来理解装饰器的作用,首先定义一个打印当前时间的函数
import time
def printCurrentTime():
currentTime = time.time()
print("当前时间戳:" + str(currentTime))
printCurrentTime() #打印:当前时间戳:1525522986.449398
考虑这样一个问题:假设我们要增强printCurrentTime()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改printCurrentTime()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
二、实现一个装饰器
我们使用装饰器来解决上述的问题,装饰器其实就是一个返回函数的高阶函数。下面我们定义一个能打印日志的decorator,装饰器函数pringLog接收一个函数作为参数,并返回一个函数,代码如下:
def pringLog(func):
def wrapper(*args, **kw):
print('---开始调用函数: %s():' % func.__name__)
result = func(*args, **kw)
print('---结束调用函数: %s():' % func.__name__)
return result
return wrapper
#注意:args与kw是Python中的可变参数,同时使用时,*args在前**kwargs在后
# *args表示任何多个无名参数,它是一个tuple;
# **kwargs表示关键字参数,它是一个dict。
然后我们需要借助Python的@语法,把decorator置于需要增加打印log功能的函数的定义处,并在此测试这个函数:
@pringLog
def printCurrentTime2():
currentTime = time.time()
print("当前时间戳:" + str(currentTime))
#测试printCurrentTime2的打印效果
printCurrentTime2()
'''
---开始调用函数: printCurrentTime2():
当前时间戳:1525523570.290186
---结束调用函数: printCurrentTime2():
'''
分析:pringLog()是一个decorator,返回一个函数,原来的printCurrentTime2()函数仍然存在,只是现在同名的printCurrentTime2变量指向了新的函数。
三、装饰器中传入参数
装饰器中也可以传入参数,具体的代码如下:
#定义一个高阶函数,返回装饰器的函数
def printLogWithText(text):
def pringLog(func):
def wrapper(*args, **kw):
print(text + '---开始调用函数: %s():' % func.__name__)
result = func(*args, **kw)
print(text + '---结束调用函数: %s():' % func.__name__)
return result
return wrapper
return pringLog
#使用装饰器
@printLogWithText("装饰器参数")
def printCurrentTime3():
currentTime = time.time()
print("当前时间戳:" + str(currentTime))
#测试printCurrentTime3的打印效果
printCurrentTime3()
'''
打印
装饰器参数---开始调用函数: printCurrentTime3():
当前时间戳:1525671291.8881621
装饰器参数---结束调用函数: printCurrentTime3():
'''
四、装饰器引发的函数名问题
经过装饰器后,如果我们在外部打印被装饰的函数名,就会发现它们的名字变化了
print(printCurrentTime2.__name__) #打印:wrapper
print(printCurrentTime3.__name__) #打印:wrapper
解决方法:使用Python内置的functools.wraps设置装饰函数
#相当于wrapper.__name__ = func.__name__
import functools
def printInfo(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('---开始调用函数: %s():' % func.__name__)
result = func(*args, **kw)
print('---结束调用函数: %s():' % func.__name__)
return result
return wrapper
@printInfo
def test():
pass
print(test.__name__) #打印test