一、 装饰器介绍
为何要用装饰器
- Python 中的装饰器是一种语法糖,可以在运行时,动态的给函数或类添加功能。
- 装饰器本质上是一个函数,使用** @ + 函数名**就是可实现绑定给函数的第二个功能 。
- 将一些通用的、特定函数的功能抽象成一个装饰器,可以重复利用这些功能
什么是装饰器
- “装饰”代指为被装饰对象添加新的功能,“器”代指器具/工具
- 装饰器的作用:就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。
- 装饰器使用场景:插入日志、性能测试、事务处理、缓存、权限校验
- 可以调用的有:函数、方法、类
- 函数装饰器分为:无参装饰器和有参装饰,二者都是使用都是需要【名称空间+函数嵌套+闭包+函数对象的组合知识】
- 使用“@”符号定义装饰器,前提是需要有一个函数作为工具然后被“@”装饰到其他函数头上,为这个函数添加功能
二、 装饰器的实现
无参装饰器的实现
装饰器的简易版本
第一种:直接使用函数对象来实现
import time
print(time.time())
def index():
time.sleep(3)
print('Welcome to the index page')
return 200
res = index
"""如果不懂函数体的代码该如何计算程序执行了多少秒呢"""
start_time = time.time()
res()
stop_time = time.time()
print('run time is %s' % (stop_time - start_time))
"""考虑到还有可能要统计其他函数的执行时间,所以还要使用函数参数的形式"""
*******************************************************************************************************************
第二种:使用参数的形式传入
def wrapper(func):
start_time = time.time()
func()
stop_time = time.time()
print('run time is %s' % (stop_time - start_time))
wrapper(index)
"""但是使用参数的话就改变了调用方式,违反了不能修改被装饰对象调用方式的原则,所以我们使用闭包函数,将值报给函数"""
*******************************************************************************************************************
第三种:使用闭包函数的形式
def timer(func):
def wrapper():
start_time = time.time()
res = func()
stop_time = time.time()
print('run time is %s' % (stop_time - start_time))
return res
return wrapper
asd = timer(index)
asd()
"""以上三种方法我们就实现了无参装饰器timer,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能。但我们忽略了若被装饰的函数是一个有参函数,便会抛出异常"""
def home(name):
time.sleep(5)
print('Welcome to the home page', name)
home = timer(home)
home('egon')
""" 因为home(‘egon’)调用的其实是wrapper(‘egon’),而函数wrapper没有参数。wrapper函数接收的参数其实是给最原始的func用的,为了能满足被装饰函数参数的所有情况,便用上*args+**kwargs组合,于是修正装饰器timer如下 """
第四种:装饰器的进阶版本解决参数问题
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
stop_time = time.time()
print('run time is %s' % (stop_time - start_time))
return res
return wrapper
使用语法糖实现
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
stop_time = time.time()
print('run time is %s' % (stop_time - start_time))
return res
return wrapper
@timer
def index():
time.sleep(1)
print('Welcome to the index page')
return 200
index()
"""在 index() 函数内部,我们调用了 time.sleep(1) 函数来模拟函数执行的耗时操作。然后,我们输出了欢迎信息,并返回了状态码 200。
执行 index() 函数时,由于它被装饰器修饰了,所以实际上是执行了闭包函数 wrapper()。在 wrapper() 函数中,我们记录了函数执行前后的时间,并输出了运行时间。最后,我们将 index() 函数的返回值 200 返回给调用者。
"""
装饰器模板
- 语法糖的书写规范:紧贴在被装饰对象的上方书写,语法糖语被装饰对象之间不可以有代码
- 语法糖的内部原理:语法糖会自动把被装饰对象的名字当成参数传给装饰器函数调用
def decorator(func):
def wrapper(*args, **kwargs):
print("Before calling the function")
result = func(*args, **kwargs)
print("After calling the function")
return result
return wrapper
@decorator
def my_function(a, b):
result = a + b
return result
print(my_function(1, 2))
执行结果:当调用 my_function(1, 2) 时,输出结果如下:
Before calling the function
After calling the function
3
双层语法糖
import time
def login_auth(func):
def auth(*args, **kwargs):
username = input('username:').strip()
password = input('password:').strip()
if username == 'kevin' and password == '123':
print('登录成功')
res = func(*args, **kwargs)
return res
else:
print('用户名密码错误')
return auth
def all_time(func1):
def time1(*args, **kwargs):
start_time = time.time()
res = func1(*args, **kwargs)
stop_time = time.time()
print('功能执行了%s秒' % (stop_time - start_time))
return res
return time1
@all_time
@login_auth
def index():
print('执行完成')
index()
多层语法糖
def outter1(func1):
print('加载了outter1')
def wrapper1(*args, **kwargs):
print('执行了wrapper1')
res1 = func1(*args, **kwargs)
return res1
return wrapper1
def outter2(func2):
print('加载了outter2')
def wrapper2(*args, **kwargs):
print('执行了wrapper2')
res2 = func2(*args, **kwargs)
return res2
return wrapper2
def outter3(func3):
print('加载了outter3')
def wrapper3(*args, **kwargs):
print('执行了wrapper3')
res3 = func3(*args, **kwargs)
return res3
return wrapper3
@outter1
@outter2
@outter3
"""
只要加了语法糖,装饰器一定会执行,不调用被装饰的函数也会执行装饰器
执行装饰器是从下往上执行,执行到最后一个装饰器之后,会把最后一个装饰器的返回值的变量名语与被装饰对象的函数名同名
语法糖不会在没有调用装饰对象之前,是不会执行内部函数的
"""
def index():
print('from index')
index()
执行顺序分析:
加载了outter3
加载了outter2
加载了outter1
执行了wrapper1
执行了wrapper2
执行了wrapper3
from index
执行顺序原理解释如下:
1.定义 outter3 函数时,先打印出 "加载了outter3";
2.进入 outter2 函数,先打印出 "加载了outter2";
3.在 outter2 函数中创建了 wrapper2 函数并返回,但并不执行 wrapper2 函数,此时将 wrapper2 函数作为参数传递给 @outter1 装饰器;
4.进入 outter1 函数,先打印出 "加载了outter1";
5.在 outter1 中将 wrapper2 函数作为参数传递给 outter1 的内部函数 wrapper1,并返回 wrapper1 函数,此时 index 函数被全局变量所指向;
6.当调用 index() 函数时,实际上是调用了经过装饰器增强后的 wrapper1 函数;
7.调用 wrapper1 函数,首先打印出 "执行了wrapper1",然后调用 wrapper2 函数;
8.调用 wrapper2 函数,首先打印出 "执行了wrapper2",然后调用 wrapper3 函数;
9.调用 wrapper3 函数,首先打印出 "执行了wrapper3",然后执行函数体中的 print('from index') 语句;
最终输出函数的执行结果 "from index"。
装饰器修复技术(了解)
- 装饰器通常会将一个函数替换成另一个函数,修改函数的属性,或者在函数周围添加一些代码来实现某些功能,这可能会导致函数元数据信息的丢失,比如函数名、文档字符串、参数列表等等,为了避免这种情况,可以使用 wraps 装饰器来修饰装饰器本身,从而确保被修饰的函数保留了其原有的元数据信息不变。
- wraps 装饰器本身也是一个装饰器,它接受一个函数作为参数,并返回一个包装函数。wraps 装饰器会将包装函数的名称、文档字符串、参数列表等元数据信息更新为原函数的元数据信息,从而确保被修饰的函数可以正确地保留原有的元数据信息。
- 使用方法:导入 from functools import wraps在装饰器的外部与内部函数之间定义 @wraps(func)
- wraps 装饰器确保了被装饰的函数 say_hello 保留了其原本的元数据信息,包括函数名和文档字符串。这样,即使在嵌套多个装饰器的情况下,也能够正确地保留函数的属性信息。
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Before the function is called.')
result = func(*args, **kwargs)
print('After the function is called.')
return result
return wrapper
@my_decorator
def say_hello(name):
"""A function that says hello"""
print(f'Hello, {name}!')
print(say_hello.__name__)
print(say_hello.__doc__)
装饰器之登录认证功能
is_login = {'is_login':False}
def login_auth(func):
def login(*args, **kwargs):
username = input('username:').strip()
password = input('password:').strip()
if username == 'kevin' and password == '123':
print('登录成功')
res = func(*args, **kwargs)
is_login['is_login'] = True
return res
else:
print('用户名或者密码错误')
return login
@login_auth
def user_login():
print('认证成功')
res = user_login
res()
有参装饰器的实现
- 有参装饰器本质上就是在普通装饰器头上再嵌套一层函数用来传递参数
- 当我们使用最外层的函数传递参数是,语法糖后面的括号中也需要传递相应的参数
def repeat(count):
def decorator_func(func):
def wrapper_func(*args, **kwargs):
for i in range(count):
res = func(*args, **kwargs)
return res
return wrapper_func
return decorator_func
"""
在 repeat() 函数内部,定义了一个闭包函数 decorator_func(),用于接受被修饰函数 func。在 decorator_func() 函数内部,再定义了一个闭包函数 wrapper_func(),用于实现装饰器的具体功能。
在 wrapper_func() 函数内部,使用一个循环来控制被修饰函数的执行次数。在每次循环中,我们尝试执行被修饰函数,并返回其结果。最终,我们将 wrapper_func() 函数作为修饰器的返回值。
"""
@repeat(3)
def greet(name):
print("Hello, %s!" % name)
"""
当我们执行 greet('Alice') 函数时,它实际上已经被修饰成了 repeat(3)(greet)('Alice') 这样的形式,即先将 greet 函数传递给 decorator_func(),再将返回的 wrapper_func 函数作为结果返回,并在其基础上执行函数 greet('Alice')。
由于装饰器 repeat() 是一个带参数的装饰器,所以要在装饰器名称后面加上括号并传入参数值,来指定装饰器的参数值。在这个例子中,我们使用 @repeat(3) 来指定函数 greet() 需要重复执行 3 次。
"""
res1 = greet
res()
Hello, Alice!
Hello, Alice!
Hello, Alice!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具