装饰器
一、无参装饰器
1.1什么是装饰器
器指的是工具,而程序中的函数就是具备某一功能的工具,所以装饰器指的是为被装饰器对象添加额外功能。因此定义装饰器就是定义一个函数,只不过该函数的功能是用来为其他函数添加额外的功能
需要注意的是:
- 装饰器本身其实是可以任意可调用的对象
- 被装饰的对象也可以是任意可调用的对象
1.2为什么要用装饰器
如果我们已经上线了一个项目,我们需要修改某一个方法,但是我们不想修改方法的使用方法,这个时候可以使用装饰器。因为软件的维护应该遵循开放封闭原则,即软件一旦上线运行后,软件的维护对修改源代码是封闭的,对扩展功能指的是开放的。
装饰器的实现必须遵循两大原则:
- 不修改被装饰对象的源代码
- 不修改被装饰对象的调用方式
装饰器其实就是在遵循以上两个原则的前提下为被装饰对象添加新功能。
1.3怎么用装饰器
改变源代码
我们简单的理解装饰器就是原本的函数增加功能,即给被装饰对象增加功能,怎么做呢,就是将被装饰对象的函数名,当作参数的形式传入装饰器函数中,然后将装饰器函数赋值给一个与被装饰对象函数名相同的变量名,以此来达到以假乱真的目的
import time
def index():
start = time.time()
print('welcome to index')
time.sleep(1)
end = time.time()
print(F"index run time is {start-end}")
index()
welcome to index
index run time is -1.0005733966827393
编写重复代码
import time
def index():
print('welcome to index')
time.sleep(1)
def f2():
print('welcome to index')
time.sleep(1)
start = time.time()
index()
end = time.time()
print(F"index run time is {start-end}")
start = time.time()
f2()
end = time.time()
print(F"f2 run time is {start-end}")
welcome to index
index run time is -1.0002689361572266
welcome to index
f2 run time is -1.0004284381866455
第一种传参方式:改变了调用方式
import time
def index():
print('hello word')
time.sleep(1)
def time_count(func):
start = time.time()
func()
end = time.time()
print(f'{func} time is {start-end}')
time_count(index)
hello word
<function index at 0x000001ED6036A950> time is -1.0002200603485107
第二种传参方式:包给函数-外包
import time
def index():
print('hello word')
time.sleep(1)
def time_count(func):
def wrapper():
start = time.time()
func()
end = time.time()
print(f'{func} time is {start - end}')
return wrapper
# f = time_count(index)
# f()
index = time_count(index) # index为被装饰器函数的内存地址,即index = wrapper
index() # wrapper
hello word
<function index at 0x0000028B2045A950> time is -1.0001304149627686
1.4完善装饰器
上述的装饰器,最后调用index()的时候,其实是在调用wrapper(),因此如果原始的index()有返回值的时候,wrapper()函数的返回值应该呵index()的返回值想同,也就是说,我们需要同步原始的index()和wrapper()方法的返回值。
往往我们的函数是由返回值的,这个时候我们就需要让装饰器函数的返回值与被装饰对象函数的返回相同,
import time
def index():
print('hello word')
time.sleep(1)
return 123
def time_count(func):
def wrapper():
start = time.time()
res = func()
end = time.time()
print(f'{func} time is {start - end}')
return res
return wrapper
# f = time_count(index)
# f()
index = time_count(index) # index为被装饰器函数的内存地址,即index = wrapper
res = index()
print(f'res:{res}')
hello word
<function index at 0x000001AF8FE6A950> time is -1.000152349472046
res:123
如果原始的index()方法需要传参,那么我们之前的装饰器是无法实现该功能,由于有wrapper() = index(),所以给wrapper()方法传参即可。
import time
def index():
print('hello word')
time.sleep(1)
return 123
def home(name):
print(f'hello {name} my brother')
time.sleep(1)
return name
def time_count(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f'{func} time is {start - end}')
return res
return wrapper
# f = time_count(index)
# f()
home = time_count(home) # index为被装饰器函数的内存地址,即index = wrapper
res = home('egon')
print(f'res:{res}')
hello egon my brother
<function home at 0x000002932089A9D8> time is -1.0009005069732666
res:egon
1.5装饰器语法糖
在被装饰函数正上方,并且是单独一行写上@装饰器名
这样我们就可以不用写多余的代码直接调用被装饰对象的函数名就可以了
import time
def time_count(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f'{func} time is {start - end}')
return res
return wrapper
@time_count # home = time_count(home)
def home(name):
print(f'hello {name} my brother')
time.sleep(1)
return name
@time_count # index = time_count(index)
def index():
print('hello word')
time.sleep(1)
return 123
res = home('egon')
print(f'res:{res}')
hello egon my brother
<function home at 0x000001EBDF02A9D8> time is -1.000500202178955
res:egon
1.6装饰器模板(很重要,要背)
先定义有参函数,在有参函数内定义
def deco(func):
def wrapper(*args,**kwargs):
res = func(*args,**kwargs)
return res
return wrapper
二、有参装饰器
无参装饰器只套了两层,本节将讲一个套三层的装饰器——有参装饰器,但现在我们先实现一个用户登录注册的装饰器。
import time
current_user = {'username': None}
def login(func):
# func = 最原始的index
def wrapper(*args, **kwargs):
if current_user['username']:
res = func(*args, **kwargs)
return res
user = input('username:').strip()
pwd = input('password:').strip()
if user == 'nick' and pwd == '123':
print('登录成功')
current_user['user'] = user
res = func(*args, **kwargs)
return res
else:
print('失败了')
return wrapper
@login
def home(name):
print(f'hello {name} my brother')
time.sleep(1)
return name
@login
def index():
print('hello word')
time.sleep(1)
return 123
res = index()
username:nick
password:123
登录成功
hello word
对于上面对的登陆注册,我们把用户登录成功的信息写入内存中。但是在工业上,用户的信息可以存在文本中、MySQL中、mongodb当中,但是我们只让用户信息来自于file
的用户可以认证。因此我们可以改写上述的装饰器。
import time
current_user = {'username': None}
def login(func):
# func = 最原始的index
def wrapper(*args, **kwargs):
if current_user['username']:
res = func(*args, **kwargs)
return res
user = input('username:').strip()
pwd = input('password:').strip()
engine = 'file'
if engine == 'file':
print('base of file')
if user == 'nick' and pwd == '123':
print('登录成功')
current_user['user'] = user
res = func(*args, **kwargs)
return res
else:
print('失败了')
elif engine == 'mongodb':
print('base of mongodb')
else:
print('default')
return wrapper
@login
def home(name):
print(f'hello {name} my brother')
time.sleep(1)
@login
def index():
print('hello word')
time.sleep(1)
res = index()
username:nick
password:123
base of file
登录成功
hello word
2.1三层闭包
def f1(y):
def f2():
x = 1
def f3():
print(f"x: {x}")
print(f"y: {y}")
return f3
return f2
f2 = f1(2)
f3 = f2()
f3()
x: 1
y: 2
现在需求改了,我们需要判断用户动态的获取用户密码的方式,如果是file
类型的,我们则让用户进行认证。因此我们可以使用有参装饰器。
import time
current_uesr = {'username': None}
def auth(engine='file'):
def login(func):
# func = 最原始的index
def wrapper(*args, **kwargs):
if current_user['username']:
res = func(*args, **kwargs)
return res
user = input('username: ').strip()
pwd = input('password: ').strip()
if engine == 'file':
print('base of file')
if user == 'nick' and pwd == '123':
print('login successful')
current_uesr['usre'] = user
res = func(*args, **kwargs)
return res
else:
print('user or password error')
elif engine == 'mysql':
print('base of mysql, please base of file')
elif engine == 'mongodb':
print('base of mongodb, please base of file')
else:
print('please base of file')
return wrapper
return login
@auth(engine='mysql')
def home(name):
print(f"welcome {name} to home page")
time.sleep(1)
@auth(engine='file')
def index():
print('welcome to index')
time.sleep(1)
res = index()
username: nick
password: 123
base of file
login successful
welcome to index
由于两层的装饰器,参数必须得固定位func
,但是三层的装饰器解除了这个限制。我们不仅仅可以使用上述单个参数的三层装饰器,多个参数的只需要在三层装饰器中多加入几个参数即可。也就是说装饰器三层即可,多加一层反倒无用。