什么是装饰器
装饰器顾名思义就是用来装饰对象的一种特殊语法,装饰的对象会获取装饰器提供的额外功能。装饰器的本质是一个函数,用来装饰类或函数,通过这种方法置入通用功能代码降低程序的复杂度,并且可以通过给被装饰对象添加不同的装饰器来增加不同的额外功能。装饰器可以一般用来做权限验证,比如时间范围检查,或者做登录权限验证。
装饰器的使用
因为装饰器的本质是函数,所以一个函数或者类添加了装饰器,就相当于将被装饰的对象作为参数传进装饰器中,如果装饰器是有参数的,那么就相当于先执行装饰器函数,执行结果作为真正的装饰器去装饰对象。
1. 不带参数的单个装饰器
@deco
def func(): pass
等价于:deco(func)
2. 带参数的单个装饰器
@deco(arg)
def func(): pass
等价于: deco(arg)(func)
# 这里先声明deco(arg)返回一个对象,该对象才是以func为参数的装饰器
3. 多个装饰器,并且带参数
@deco1(arg)
@deco2
def func(): pass
等价于:deco1(arg)(deco2(func))
# 这里先声明deco1(arg),再deco2(func),然后前者作为后者的参数传递,这里涉及到多个装饰器的执行顺序问题,下面再讲。
functools.wraps
上面了解到装饰器将被装饰的函数作为参数,那么在装饰器中返回的函数名其实已经变成了装饰器外层函数返回函数名。
from functools import wraps
def deco(func):
def inner():
print('func name:', func.__name__, 1)
return func()
return inner
@deco
def func():
print('func name:', func.__name__, 2)
func()
# 输出
func name: func, 1
func name: inner, 2
如果想被装饰函数保持原来的属性,可以使用functools.wraps装饰器
from functools import wraps
def deco(func):
@wraps(func)
def inner():
print('func name:', func.__name__, 1)
return func()
return inner
@deco
def func():
print('func name:', func.__name__, 2)
func()
# 输出
func name: func 1
func name: func 2
多个装饰器的执行顺序
def deco1(func):
print(1111)
def inner():
print(2222)
return func()
return inner
def deco2(func):
print(3333)
def inner():
print(4444)
return func()
return inner
@deco1
@deco2
def func():
print(5555)
func()
# 输出
3333
1111
2222
4444
5555
从上面的代码中可以看到,代码所走的顺序:
1:内层装饰器的外层函数
2:外层装饰器的外层函数
3:外层装饰器的内层函数
4:内层装饰器的内层函数
5:回到被装饰函数
原因:多个装饰器情况会先执行外层装饰器,再执行内层装饰器
我们知道装饰器本质上是一个函数,函数在python解释器解析时声明,从上面我们可以知道这相当于 func = deco1(deco2(func)) ,因此在声明函数(函数声明从上往下)时先声明deco1,在声明deco2,然后函数声明完毕,我们上面的func实际上已经变成被装饰器装饰后返回的新对象。执行func(),先执行外层装饰器,再执行内部装饰器,最后回到被装饰函数,因此就造成如上的打印顺序。
常用装饰器
1. 输出运行时间
def timeit(fun):
def wrapper(*args, **kwargs):
star_time=time.time() # 记录函数开始运行的时间
res = fun(*args, **kwargs)
end_time=time.time() # 记录函数运行完后的时间
print('运行时间:%.6f' %(end_time-star_time)) # 打印出函数运行的时间
return res
return wrapper
2. 检查是否在允许的时间范围内
def check_time(func):
def wrapper(*args, **kwargs):
if is_work_time():
return func(*args, **kwargs)
return ''
return wrapper
# 判断是否为工作日,工作日返回1,非工作日返回0
def is_work_time():
day_week = datetime.datetime.now().weekday()
begin = datetime.datetime.now().strftime("%Y-%m-%d") + ' ' + '09:00:00'
end = datetime.datetime.now().strftime("%Y-%m-%d") + ' ' + '18:00:00'
begin_seconds = time.time() - time.mktime(time.strptime(begin, '%Y-%m-%d %H:%M:%S'))
end_seconds = time.time() - time.mktime(time.strptime(end, '%Y-%m-%d %H:%M:%S'))
if int(day_week) in range(5) and int(begin_seconds) > 0 and int(end_seconds) < 0:
return 1
else:
return 0