python装饰器
python装饰器
为何要用装饰器
软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。
对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。
# 软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃;
# 而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。
什么是装饰器
概括地讲,装饰器的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。
装饰器经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景,装饰器是解决这类问题的绝佳设计,有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
# 提示:可调用对象有函数,方法或者类,此处我们单以本章主题函数为例,来介绍函数装饰器,并且被装饰的对象也是函数。
无参装饰器
函数装饰器分为:无参装饰器和有参装饰两种,二者的实现原理一样,都是'函数嵌套+闭包+函数对象'的组合使用的产物。
# 统计程序运行时长的装饰器
import time
def timer(func):
def inner():
start = time.time()
res = func()
end = time.time()
print(f'{func}运行用时:{end - start}s')
return res
return inner
def foo():
time.sleep(2)
print('this is foo')
foo = timer(foo) # timer是一个装饰器,
foo() # 此时调用的foo,其实是调用timer内层定义的inner函数
# 如果被装饰函数需要参数,则装饰器内的inner函数也要有形参。因为本质上执行foo()就是执行装饰器的内层函数inner
import time
def timer(func):
def inner(name):
start = time.time()
res = func(name)
end = time.time()
print(f'{func}运行用时:{end - start}s')
return res
return inner
def foo(name):
time.sleep(2)
print(f'hello {name}')
foo = timer(foo)
foo('the3times')
# 这样的话,利用之前学的可变长度的参数,就可以做一个统计函数运行时长的装饰器模板
import time
def timer(func):
def inner(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f'{func}运行用时:{end - start}s')
return res
return inner
def foo(name):
time.sleep(2)
print(f'hello {name}')
foo = timer(foo)
foo('the3times')
所谓装饰器,看起来就是内存地址偷梁换柱的障眼法,其实是利用的python的名称空间分类保管每个函数对象。
语法糖
语法糖是python的一种语法风格,本质就是上述的装饰器。
import time
def timer(func):
def inner(*args, **kwargs):
time.sleep(1) # 让inner函数睡眠1s
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f'{func}运行用时:{end - start}s')
return res
return inner
@timer # 这样写就相当于 foo = timer(foo)
def foo(name):
time.sleep(2)
print(f'hello {name}')
# foo = timer(foo)
foo('the3times')
# output:
hello the3times
<function foo at 0x000001E99EF65EE0>运行用时:2.000154733657837s
语法糖是为了简化定义装饰器的流程。并且这样子看上去更像是foo函数被装饰了一样,更形象。
多层装饰器
def timer(func):
def inner(*args, **kwargs):
time.sleep(1)
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f'{func}运行用时:{end - start}s')
return res
return inner
@timer
@timer
def foo(name):
time.sleep(2)
print(f'hello {name}')
foo('the3times')
# output:
hello the3times
<function foo at 0x000002968B035F70>运行用时:2.000859260559082s
<function timer.<locals>.inner at 0x000002968B038280>运行用时:3.001034736633301s
# 放了两个装饰器,就相当于 foo = timer(timer(foo))
# 先把原函数foo当timer的实参,返回inner交给timer再当实参,即先后统计了foo函数运行时长和inner的运行时长
有参装饰器
有参装饰器,就是使用装饰器时可以给装饰器传参数。
import time
def auth(flag):
def timer(func):
def inner(*args, **kwargs):
if flag == 'am':
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f'{func}运行用时(am):{end - start}s')
return res
elif flag == 'pm':
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f'{func}运行用时(pm):{end - start}s')
return res
return inner
return timer
# 下面的语法糖:
# 先调用auto('am'),得到@timer,此时timer是一个闭包函数;
# 包含了对外部作用域名字flag的引用,@timer的语法意义与无参装饰器一样
@auth('am') # 相当于 @timer 只不过此时timer中有了判断需要的参数'am'
def foo(name):
time.sleep(2)
print(f'hello {name}')
foo('jack')
有参装饰器模板 使用装饰器嵌套三层就可以满足几乎全部的需求
def auth(argument):
def wrapper(func):
def inner(*args, **Kwargs):
func(*args, **kwargs)
return inner
return wrapper
@auth('参数') # @wrapper
def index():
pass
补充思考
保留被装饰函数的属性
可以使用help(函数名)来查看函数的文档注释,本质就是查看函数的doc属性,但对于被装饰之后的函数,查看文档注释
import time
def timer(func):
def inner(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f'{func}运行用时:{end - start}s')
return res
return inner
@timer
def foo(name):
"""
hello i am foo
:param name:
:return:
"""
time.sleep(2)
print(f'hello {name}')
print(help(foo))
当注释掉语法糖时那行代码时,打印的结果是查看foo函数的信息
Help on function foo in module __main__:
foo(name)
hello i am foo
:param name:
:return:
None
使用装饰器时,打印的结果是查看foo函数的信息,变成了装饰器内部函数inner的信息了,这不是我们希望的。
Help on function inner in module __main__:
inner(*args, **kwargs)
None
如果想要保留住被装饰函数的这个属性,需要修正装饰器。
def timer(func):
def inner(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f'{func}运行用时:{end - start}s')
return res
inner.__doc__ = func.__doc__ # 手动修改
inner.__name__ = func.__name__
return inner
按照上述方式来实现保留原函数属性过于麻烦,functools模块下提供一个装饰器wraps专门用来帮我们实现这件事,用法如下
from functools import wraps
def timer(func):
@wraps(func)
def inner(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f'{func}运行用时:{end - start}s')
return res
return inner
小测试
测试1:
import time
def wrapper2(func):
def inner2(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f'用时2:{end-start}')
return res
return inner2
@wrapper2
def wrapper(func):
def inner(*args, **kwargs):
time.sleep(1) # 睡眠在inner里面
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f'用时:{end-start}')
return res
return inner
@wrapper
def my_func():
time.sleep(2)
print(f'欢迎')
my_func()
# output:
用时2:0.0
欢迎
用时:2.0001373291015625
测试2:
import time
def wrapper2(func):
def inner2(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f'用时2:{end-start}')
return res
return inner2
@wrapper2
def wrapper(func):
time.sleep(1) # 睡眠在wrapper里面
def inner(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f'用时:{end-start}')
return res
return inner
@wrapper
def my_func():
time.sleep(2)
print(f'欢迎')
my_func()
# output:
用时2:1.0001418590545654
欢迎
用时:2.0003206729888916
测试2思路:
# 万变不离其宗:
wrapper = wrapper2(wrapper)
my_func = wrapper(my_func)
my_func()