装饰器实际上就是一种在程序运行期间动态地增加功能的方式,
它既不修改被装饰函数的源代码,也不改变原函数的调用方式。
例如现有一个time()函数:
def time():
print('12:00')
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数:
now = time
now()
结果:
12:00
本质上装饰器就是一个返回函数的高阶函数。
现在希望time()函数可以增加一个输出日期的功能,但又不修改原本的定义,可以设定一个装饰器:
def date(func):
def wrapper(*args, **kw):
print('2018-12-18')
return func(*args, **kw)
return(wrapper)
date即为一个装饰器,接受一个函数作为参数,并返回一个函数
使用python的@语法将装饰器放在所要装饰的函数上:
@date
def time():
print('12:00')
time()
结果:
2018-12-18
12:00
把 @date放在所装饰的函数上面 等价于 time = date(time)
由于date()是一个装饰器,返回一个函数,所以,原来的time()函数仍然存在,只是现在同名的time变量指向了新的函数,于是调用time()将执行新函数,即在date()函数中返回的wrapper()函数。
wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先输出日期,再紧接着调用原始函数。
如果装饰器本身需要传入参数,那就需要定义一个返回装饰器的高阶函数。例如,要自定义data的文本:
def date(text):
def decorator(func):
def wrapper(*args, **kw):
print('{}:2018-12-18'.format(text))
return func(*args, **kw)
return(wrapper)
return decorator
进行装饰:
@date('runtime')
def time():
print('12:00')
time()
结果:
runtime:2018-12-18
12:00
此时的 @date(‘runtime’) 等价于 time = date(‘runtime’)(time)
首先执行time(‘runtime’),返回的是decorator函数,再调用返回的函数,参数是time函数,返回值最终是wrapper函数。
现在使用函数对象内置的 __name__属性(可以得到函数的名字) 来看看用装饰器装饰过后的函数属性是否会发生变化:
print(time.__name__)
结果:
wrapper
发现函数名由原来的time变成了wrapper,此时应使用python内置的functools.wraps将原始函数time()的__name__等属性复制到wrapper()函数中:
import functools
def date(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('{}:2018-12-18'.format(text))
return func(*args, **kw)
return(wrapper)
return decorator
即在定义wrapper函数前加一句 **@functools.wraps(func)**就可以了。
如要要实现将不带参数的装饰器和带参数的装饰器合并一起,可参考如下写法:
import functools
def date(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('{}:2018-12-18'.format(text) if not callable(text) else '2018-12-18')
return func(*args, **kw)
return wrapper
return decorator(text) if callable(text) else decorator
callable(text) 作用是判断参数对象text是否为可回调对象,是的话返回True,否则返回False。