什么是装饰器(decorator)?
顾名思义,它是一个用来装饰函数的东西。具体点,装饰器是一个用来增加函数功能 ("装饰"一词就这么来的) 的东西。
装饰器由两部分组成:
- 装饰函数,例如函数名叫 dec。
- 一行 “@dec” 加在被装饰的函数上。
如果通过装饰器来增加函数的功能,有以下优点:
- 不改变被增强函数的内部代码。
- 不改变函数的调用方式。
如何增加函数的功能
假如有以下函数。
import time
def test():
print('running test function...')
time.sleep(0.5)
test()
这个 test 函数的内部代码如上所示,调用方式为 test()。
现在,你想增加这个函数的功能:除了输出一句"running test function…"之外,还输出从运行到结束函数占用的时间。
要求:不可以改变 test 函数的内部代码,但可以改变函数的调用方式
import time
def test():
print('test function')
time.sleep(0.5)
def timer(func):
start = time.time()
func()
end = time.time()
print('running time:', end - start)
timer(test)
上面的代码中,timer 函数接受的参数是一个函数名。func 参数接收到之后,就可以通过 func() 调用传来的 test 函数。
好了,现在又有了新的要求:不可以改变 test 函数的内部代码,也不可以改变函数的调用方式
于是就有了下面的代码:
import time
def decorator(func):
def wrapper():
start = time.time()
func()
end = time.time()
print('running time:', end - start)
return wrapper
def test():
print('test function')
time.sleep(0.5)
test = decorator(test)
test()
decorator 函数就是一个装饰函数,它接收一个函数对象,返回一个函数对象,中间过程就是对接收的函数对象封装的过程。
我是这么理解的:传进来的 func 函数对象是一小盘沙子,但是你想要一大盘沙子,内部嵌套定义的 wrapper 函数是一个大盘子,把那一小盘沙子(func 函数)放进大盘子里,然后再加上些必要的沙子(监测时间的代码),这一大盘沙子(wrapper函数)就是你想要的了。
请你仔细阅读上面的代码,这段代码是一个最简单的装饰器的本质。里面包含了嵌套函数、函数名传参的相关知识,如果无法理解,不建议继续往下读。
真正的装饰器
删除上面倒数第二行的代码 test = decorator(test)
,新增加一行 @decorator
,其他代码一点未变。
import time
def decorator(func):
def wrapper():
start = time.time()
func()
end = time.time()
print('running time:', end - start)
return wrapper
@decorator # 增加这一行
def test():
print('test function')
time.sleep(0.5)
# test = decorator(test) 删除这一行
test()
这便是装饰器 — 装饰函数 decorator + “@decorator”。
其实,@decorator
这句话完全等价于 test = decorator(test)
。
运行代码,得到结果
test function
running time: 0.5002768039703369
装饰器的实质
其实,接着上部分关于沙子的比喻,装饰器实质上就是把小盘沙子封装了一下,用大些的盘子装起来。也就是装饰之后的test函数其实是 wrapper 函数:
import time
def decorator(func):
def wrapper():
start = time.time()
func()
end = time.time()
print('running time:', end - start)
print(id(wrapper))
return wrapper
@decorator # 增加这一行
def test():
print('test function')
time.sleep(0.5)
# test = decorator(test) 删除这一行
print(id(test))
2829141796248
2829141796248
结果在不一样的电脑上是不一样的,但是能确定的是输出的两个函数入口地址是相同的。
这也就验证了上面的一句话:
@decorator
这句话完全等价于test = decorator(test)
。
带参数的装饰器
废话不多说,上代码,修改部分已在注释部分说明。
import time
def decorator(func):
def wrapper(param): # wrapper 函数加上参数
start = time.time()
func(param) # 调用时加上参数
end = time.time()
print('running time:', end - start)
return wrapper
@decorator
def test(param): # 若被修饰函数带参数,则在 wrapper 函数做相应修改
print('test function')
time.sleep(0.5)
test()
看了几篇博客后对于装饰器的浅薄理解,如果有不对处请指正。