1. 装饰器本质
装饰器实际上是给函数添加额外功能的函数。
函数的编写理论上要遵循开放封闭的原则,
开放是指该函数功能可以扩展,
封闭是指当函数已经实现预期功能,就不要更改内部代码了,此时要想添加功能,就要使用装饰器。
2. 举个例子
比如说想要统计函数的运行时间:
import time
def dance():
time.sleep(3)
print("-----","我在跳舞","-----")
def sing():
time.sleep(3)
print("-----","我在唱歌","-----")
def sleep():
time.sleep(10)
print("------","我在睡觉","-----")
def cook():
time.sleep(5)
print("------","我在做饭","-----")
统计dance函数的运行时间,一般有两种做法:
# 第一种做法
def dance():
start_time = time.time()
time.sleep(3)
print("-----","我在跳舞","-----")
stop_time = time.time()
print("dance函数的运行时间:",stop_time-start_time)
dance()
#这种做法显然不符合函数的开放封闭原则,修改了函数内部的代码
# 第二种方法
start_time = time.time()
dance()
stop_time = time.time()
print("dance函数的运行时间:",stop_time-start_time)
# ----- 我在跳舞 -----
# dance函数的运行时间: 3.0120158195495605
#这种做法若是要统计个别函数的运行时间倒是可以,但是若有多个函数需要统计,就会显得特别麻烦
使用装饰器的做法
# 该函数就是装饰器,其实就是闭包
def total_time(func):
def inner():
start_time = time.time()
func()
stop_time = time.time()
print("dance函数的运行时间:",stop_time-start_time)
return inner
@total_time
def dance():
time.sleep(3)
print("-----","我在跳舞","-----")
dance()
# 运行结果:
# ----- 我在跳舞 -----
# dance函数的运行时间: 3.0112452507019043
可以发现,给函数添加功能时使用装饰器不仅遵循了开放封闭原则,同时也简化了代码,只要给每个函数之前加上@total_time就可以统计其运行时间了。
思考:不用闭包,这样写为什么不行
def total_time(func):
start_time = time.time()
func()
stop_time = time.time()
print("dance函数的运行时间:",stop_time-start_time)
"""
----- 我在跳舞 -----
dance函数的运行时间: 3.0044522285461426
Traceback (most recent call last):
File "c:\Users\q1994\Desktop\学习\Pyhton\21-装饰器.py", line 29, in <module>
dance()
TypeError: 'NoneType' object is not callable
"""
3. 装饰器具体实现
那上边举的例子进行解析:
1. 需要对函数dance进行功能上的添加,创建了闭包函数total_time
def total_time(func):
def inner():
start_time = time.time()
func()
stop_time = time.time()
print("dance函数的运行时间:",stop_time-start_time)
return inner
2. 执行dance()时,会找到dance函数的定义处,查看上面是否存在@total_time
@total_time
def dance():
time.sleep(3)
print("-----","我在跳舞","-----")
3. 存在@total_time,就先执行total_time(func),将dance作为实参传递给total_time函数
total_time(dance)
4. total_time函数返回inner函数,并将返回值赋给变量dance,也就是说此时执行的dance()实际上是执行inner(),原来的dance函数已经被放进了inner函数内部了。这样就实现了功能扩展
def inner():
start_time = time.time()
dance()
stop_time = time.time()
print("dance函数的运行时间:",stop_time-start_time)
5. 解答一下上面的思考题:可以明显发现最后执行dance()实际上是执行inner(),而原本的dance函数被放在了inner内部,巧妙的将功能扩展,很好的保持了代码的整体一致性。
4. 再举个例子
实现功能:在原来函数的基础上实现身份验证
应用场景:将每一个函数视为独立的功能,使用这些功能时需要进行身份验证,禁止未被授权的人使用这些功能。
def identity(func):
user = input("请输入账号:")
password = input("请输入密码:")
def inner():
if "ZJ" != user:
print("账号错误")
if "123456" != password:
print("密码错误")
else:
print("登录成功")
func()
return inner
@identity
def money():
print("这是一个转账功能")
money()
"""
请输入账号:ZJ
请输入密码:123456
登录成功
这是一个转账功能
"""
上面代码理解装饰器执行行为可理解
1. money作为实参传递给identity函数,money接收identity的返回值inner
2. 调用money()就相当于调用inner()
3. inner内部的func保存的是原来的money函数对象
5. 装饰器的不同用法
1. 对无参数的函数进行装饰
上述两个例子都是。
2. 对有参数的函数进行装饰
# 原来功能:对输入的浮点数进行加法运算
# 添加功能:将浮点数先向下取整再运算
import math
def int_computer(func):
def inner(a,b):
a = math.floor(a)
b = math.floor(b)
func(a,b)
return inner
@int_computer
def float_computer(a,b):
c = a+b
print(c)
float_computer(3.12,4.23)
#运行结果:7
已经知道执行float_computer()实际就是执行inner(),故装饰器内部的inner需要接收参数,在inner中对参数处理完后,再将参数传递给原本的float_computer。
3. 对带有return的函数进行装饰
这个是错误的:
import time
def cur_time(func):
def inner():
print(time.time())
func()
return inner
@cur_time
def run():
return "我正在跑步----"
print(run())
# 1696734497.0567214
# None
这个是正确的:
import time
def cur_time(func):
def inner():
print(time.time())
return func()
return inner
@cur_time
def run():
return "我正在跑步----"
print(run())
# 1696734605.7440841
# 我正在跑步----
两者的区别就在于:
#第一种
func()
#第二种
return func()
原因在于:
# 第一种情况相当于
def inner():
print(time.time())
run()
#即
def inner():
print(time.time())
"我正在跑步----"
#第二种情况相当于
def inner():
print(time.time())
return run()
#即
def inner():
print(time.time())
return "我正在跑步----"
4. 多个装饰器对同一个函数进行修饰
# 功能实现:输入名字,补全信息
def name(func):
def inner():
return func()+'姓名:IU-----'
return inner
def age(func):
def inner():
return func()+'年龄:30'
return inner
@age
@name
def info():
return "打印信息-----"
print(info())
多个装饰器修饰同一个函数的具体实现流程:
1. 首先是执行的@age,进入到age函数的闭包函数inner()内部
2. 在age的inner中碰到func()即原来的info函数,就再找到下一个@name
3. 进入到name函数的闭包函数inner()中,碰到func()即原来的info,没有其它的装饰器了,就到此为止,否则继续往下找
4. 此时,age函数的inner()中的func()实际上是name函数中的inner()
5. 最后,执行name函数中的inner(),返回值返回到age函数中的inner()
6. 总的来说,就是一层一层往下调,返回值时从最深层往外返回
5. 装饰器带参数
# 设置睡觉时间
import time
def set_time(timeout):
def inner1(func):
def inner2():
func()
time.sleep(timeout)
print("睡醒了-----")
return inner2
return inner1
@set_time(5)
def sleep():
print("开始睡觉-----")
sleep()
区别就在于最外层函数是用于接收实参的,第二层才接受函数。
6. 用类作为装饰器
上面的例子都是用闭包函数来作为装饰器的,现在讨论使用类作为装饰器的情况。
类作为装饰器必须要实现__call__方法,这样的类创建的对象是callable对象。
Python中callable对象一般都是函数
class A():
def __call__(self):
print("该类创建的对象可以作为函数直接执行")
a = A()
a()
# 运行结果:该类创建的对象可以作为函数直接执行
类作为装饰器的例子:
class Test():
def __init__(self, func):
print("---初始化---")
print("func name is %s" % func.__name__)
self.__func = func
def __call__(self):
print("---装饰器中的功能---")
self.__func()
#说明:
#1. 当用Test来装作装饰器对test函数进行装饰的时候,首先会创建Test的实例对象
# 并且会把test这个函数名当做参数传递到__init__方法中
# 即在__init__方法中的属性__func指向了test指向的函数
#2. test指向了用Test创建出来的实例对象
#3. 当在使用test()进行调用时,就相当于直接调用实例对象,因此会调用这个对象的__call__方法
#4. 为了能够在__call__方法中调用原来test指向的函数体,所以在__init__方法中就需要一个实例属性来保存这个函数体的引用
# 所以才有了self.__func = func这句代码,从而在调用__call__方法中能够调用到test之前的函数体
@Test
def test():
print("----test---")
test() # 如果把这句话注释,重新运行程序,依然会看到"--初始化--"