有时候我们好不容易实现了某个功能,假设有一个处理图片的功能。
def process_img():
pass
现在有一个新的需求,要求必须是注册登录的用户才能使用,否则无法使用。
这个时候你可能会去拆解你已经写好的process_img函数,看如何将登录注册的代码融合进去。
搞了半天,兴许成功了,也有可能原来能够工作的代码变的不能工作了,这个时候你会想,能不能将两个方法分开实现,我原先写好的代码不动,仅仅只增加一个新的功能代码,将它们像搭积木一样拼在一起即可。
装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能,并让代码保持简短。
装饰器(Decorator)是 Python 非常重要的组成部分,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。装饰器本质上是一个Python函数,它经常用于有切面需求的场景,比如:插入日志、权限校验等场景。
案例1:计算程序运行的时长
正常测量一段代码运行的时长可以这样写:
import time
def test():
s = 0
for i in range(10000000):
s = s + i
print(f'计算结果为{s}')
print(f'\n{test.__name__} 函数 开始运行.')
start_time = time.time()
# 运行函数
test()
end_time = time.time()
print(f'{test.__name__} 函数结束运行,总共耗时 {end_time - start_time} 秒')
结果:
test 函数 开始运行.
计算结果为49999995000000
test 函数结束运行,总共耗时 1.3232624530792236 秒
现在我又有一个函数,需要计算其运行时间。
def test2():
s = 0
for i in range(50000000):
s = s + i
print(f'计算结果为{s}')
简单,很容易就会想到将函数名改成test2()就可以了。
如果有更多需要计算运行时间的函数呢?能否封装一个函数,用一个变量来替换每次需要更改的函数名呢?
好了,一个专门计时的函数诞生了。
def timer(func):
print(f'\n{func.__name__} 函数 开始运行.')
start_time = time.time()
# 运行函数
func()
end_time = time.time()
print(f'{func.__name__} 函数结束运行,总共耗时 {end_time - start_time} 秒')
使用非常简单,直接调用,填入要测试的函数名即可。
timer(test2)
看上去非常方便,只不过需要每次调用它。
下面将其改成装饰器的写法。
def timer_deco(func):
def deco():
print(f'\n{func.__name__} 函数 开始运行.')
start_time = time.time()
# 运行函数
func()
end_time = time.time()
print(f'{func.__name__} 函数结束运行,总共耗时 {end_time - start_time} 秒')
return deco
@timer_deco
def test_deco():
s = 0
for i in range(10000000):
s = s + i
print(f'计算结果为{s}')
test_deco()
在定义test_deco函数前面加@timer_demo,就相当于给test_deco增加了一个测时功能,使用时直接调用原函数即可,是不是很方便呢。
又有一个函数要计算运行时间,这个函数还带有一个参数。
改成下面的装饰器代码即可。
def timer_deco(func):
def deco(*args, **kwargs):
print(f'\n{func.__name__} 函数 开始运行.')
start_time = time.time()
# 运行函数
func(*args, **kwargs)
end_time = time.time()
print(f'{func.__name__} 函数结束运行,总共耗时 {end_time - start_time} 秒')
return deco
@timer_deco
def test3(n):
s = 0
for i in range(n):
s = s + i
print(f'计算结果为{s}')
test3(5)
以后只要想测试某个函数的运行时间,直接在定义函数的前面加上@timer_deco即可。原来的代码不用做任何改变,就增加了一个新的功能。
案例2:首页登录装饰器
有一个进入首页的函数,正常直接调用即可进入。
def index():
print("这是首页")
现在我们有了一个新想法,要求必须是登录的账号才能进入,否则要求登录。
def index():
print("这是首页")
def required_login(fun):
def wrapper():
print("请输入账号密码")
name = input("账号:")
password = input("密码:")
if name=="admin" and password=="admin12345":
print("登录成功")
return fun()
else:
print("登录失败")
return wrapper
# 传入函数参数,返回新函数。
new_index = required_login(index)
# 调用函数
new_index()
上面调用函数的方法还不是很方便,更简便的方法,是将函数改成装饰器装饰,以后只要有需要登录的才能访问的函数,都可以用这个装饰器装饰一下即可。
@required_login
def index2():
print("这是首页")
@required_login
def article():
print("这是内容页")
index2()
print('-----------------')
article()
结果:
请输入账号密码
账号:admin
密码:12345
登录失败
-----------------
请输入账号密码
账号:admin
密码:admin12345
登录成功
这是内容页
下面又有新需求,要求能够判断是否已经登录过,登录过就直接进入,否则要求登录。
# 初始状态
state = "登录"
def get_args_required_login(state):
def required_login(fun):
def wrapper():
if state=="登录":
print("已经登录过,直接进入首页")
return fun()
else:
print("你还未登录")
name = input("账号:")
password = input("密码:")
if name=="admin" and password=="admin12345":
print("登录成功")
return fun()
else:
print("登录失败")
return wrapper
return required_login
@get_args_required_login(state)
def index3():
print("这是首页")
index3()
结果:
已经登录过,直接进入首页
这是首页
如果将状态改成未登录,结果会怎样呢?
# 更改初始状态为未登录
state = "未登录"
...
结果:
你还未登录
账号:admin
密码:admin1234
登录失败
你还未登录
账号:admin
密码:admin12345
登录成功
这是首页