decorator - yaokun123/php-wiki GitHub Wiki
装饰器
一、装饰器是什么?为什么要使用装饰器?
1.1 本质定义
装饰器是接收函数并返回新函数的[高阶函数]
它能够通过 @语法糖在不改变原函数代码的前提下,给原函数添加新的功能。
1.2 核心原理
利用闭包(内层函数引用外层函数变量)和函数作为对象(一等公民)的特性,实现“包装器”逻辑。
1.3 语法糖本质
“语法糖”是一种编程特性,它能将某些代码操作以简洁且优雅的形式呈现。
例如,在装饰器使用场景中,@decorator 作为 func = decorator(func) 的简写形式,
通过直接在函数定义上方使用 @ 符号,就能便捷地绑定装饰器,这里的 @ 便是[语法糖]
1.4 为什么要使用装饰器
使用装饰器主要有以下好处:
1、代码复用:多个函数需要相同额外功能时,封装成装饰器避免代码重复。
2、代码简洁:分离额外功能与核心逻辑,原函数专注于核心功能的实现。
3、易于维护:修改额外功能时只需调整装饰器,无需逐个修改被装饰函数。
二、装饰器如何定义及使用
2.1 装饰器使用“三步走”
1、定义装饰器函数:接收目标函数为参数,返回新的包装函数。
2、定义原函数:编写需增强功能的核心函数。
3、应用装饰器:在原函数定义前使用 @装饰器名,等价于 原函数 = 装饰器(原函数)。
2.2 装饰器的使用场景
1、计时:记录函数执行时间(使用 time.time() 计算时间差)。
2、权限校验:根据参数校验用户角色,抛出异常(如 PermissionError)。
3、类型检查:通过函数注解 @enforce_types 验证参数类型(利用 func.__annotations__)。
4、返回值增强:修改原函数返回值(如自动加固定值)。
2.3 装饰器的常见错误
1、错误调用函数:@decorator 不是 @decorator(),装饰器直接绑定函数而非调用
2、参数传递错误:未使用*args, **kwargs导致参数丢失,需在包装函数中兼容任意参数
3、循环导入:装饰器和被装饰函数相互引用时拆分代码,避免模块间循环依赖
三、基本使用
装饰器函数在模块加载时立即执行(仅执行一次),用于绑定被装饰函数,而 wrapper 函数在每次调用被装饰函数时执行。
3.1 无参数装饰器 (基本用法)
通过闭包定义内层包装函数,在调用原函数前后添加逻辑(如计时),返回包装函数。
import time
def timer(func):
def wrapper():
start = time.time() # 记录开始时间
result = func() # 执行原函数
print(f"耗时:{time.time()-start:.2f}秒") # 计算耗时
return result # 显式返回原函数结果
return wrapper
@timer
def slow_func():
time.sleep(1)
slow_func() # 输出执行时间
3.2 保留原函数元信息
使用 functools.wraps 装饰包装函数,避免原函数的名称、文档字符串等元信息被覆盖。
from functools import wraps
def decorator(func):
@wraps(func) # 保留原函数元信息(函数名、注释等)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@decorator
def example():
"""示例函数"""
pass
print(example.__name__) # 输出"example"(而非默认的"wrapper")
3.3 处理带参数的函数
包装函数使用 *args, **kwargs 接收任意参数,确保装饰器兼容不同参数形式的函数。
from functools import wraps
def logger(func):
@wraps(func) # 保留原函数元信息(如函数名、文档字符串)
def wrapper(*args, **kwargs):
print(f"调用函数:{func.__name__},参数:{args}, {kwargs}")
return func(*args, **kwargs) # 传递参数并返回原函数结果
return wrapper
@logger
def add(a, b):
return a + b
print(add(2, b=3)) # 输出参数和结果
3.4 带参数的装饰器
通过三层嵌套函数,外层接收装饰器参数,中层绑定目标函数,内层包装函数实现参数化逻辑(如重复执行函数n次)。
from functools import wraps
def repeat(n): # 第一层:装饰器工厂,接收参数
def decorator(func): # 第二层:真正的装饰器
@wraps(func)
def wrapper(*args, **kwargs): # 第三层:wrapper 函数
results = []
# 循环n次调用原函数
for _ in range(n):
results.append(func(*args, **kwargs))
return results # 返回最终结果而非None
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello {name}") # 打印欢迎语句
return f"Hello {name}" # 返回欢迎语句
greet("小帅") # 输出3次"Hello 小帅"
print(greet("小帅")) # ['Hello 小帅', 'Hello 小帅', 'Hello 小帅']
3.5 多层装饰器
装饰器绑定:my_func = decorator1(decorator2(my_func))
调用 my_func() 时的实际执行顺序:
- 进入 decorator1 的 wrapper1
- wrapper1 中执行权限校验逻辑
- wrapper1 调用 decorator2 的 wrapper2
- wrapper2 调用原始函数 my_func
- wrapper2 中执行日志记录逻辑
- 以上执行完毕后,以相反顺序返回
- 离开 decorator2 的 wrapper2
- 离开 decorator1 的 wrapper1
def decorator1(func):
def wrapper1(*args, **kwargs):
print("进入外层装饰器 wrapper1")
print("校验权限...")
# 这里可加入权限校验逻辑
result = func(*args, **kwargs) # 调用 decorator2 的 wrapper2
print("离开外层装饰器 wrapper1")
return result
return wrapper1
def decorator2(func):
def wrapper2(*args, **kwargs):
print("进入内层装饰器 wrapper2")
result = func(*args, **kwargs) # 调用原始函数
print("记录操作日志...")
# 这里可加入记录操作逻辑
print("离开内层装饰器 wrapper2")
return result
return wrapper2
@decorator1
@decorator2
def my_func():
print("执行原始函数")
# 调用被装饰后的函数
my_func()
3.6 类装饰器
装饰器不一定是函数,也可以用类来实现。装饰器函数其实是一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。
在python中,一般callable对象都是函数,但是也有例外。比如只要某个对象重写了call方法,那么这个对象就是callable的。
类装饰器通过 _call_ 方法让对象可以像函数一样被调用,适合需要保存状态的场景。
当创建一个对象后,直接去执行这个对象,那么是会抛出异常的,因为他不是callable,无法直接执行。
但进行修改后,就可以直接执行调用了,如下
class Test(object):
def __call__(self, *args, **kwargs):
print('call called')
t = Test()
print(t())
实现无参数的类装饰器
无参数的类装饰器直接接收被装饰函数作为参数,通过 __call__ 方法实现装饰逻辑:
import time
class Timer:
def __init__(self, func):
self.func = func # 保存被装饰的函数
def __call__(self, *args, **kwargs):
start_time = time.time()
result = self.func(*args, **kwargs) # 调用原函数
end_time = time.time()
print(f"{self.func.__name__} 执行时间: {end_time - start_time:.4f}秒")
return result
@Timer
def slow_function():
time.sleep(1)
print("函数执行完毕")
slow_function() # 实际调用的是 Timer 实例的 __call__ 方法
实现带参数的类装饰器
带参数的类装饰器需要在 __init__ 中接收参数,并在 __call__ 中返回包装函数
class Repeat:
def __init__(self, num_times):
self.num_times = num_times # 接收装饰器参数
def __call__(self, func):
# 这里返回一个包装函数
def wrapper(*args, **kwargs):
for _ in range(self.num_times):
result = func(*args, **kwargs)
return result
return wrapper
@Repeat(num_times=3)
def greet(name):
print(f"Hello, {name}!")
greet("Bob")
类装饰器装饰类
类装饰器可以动态修改类的行为,例如添加方法或属性:
类装饰器是一个函数,它接受一个类作为参数,并返回一个新的类或对原类进行修改。
def add_method(cls):
# 为类添加一个新方法
def new_method(self):
return f"这是 {self.__class__.__name__} 的新方法"
# 给类绑定新方法
cls.new_method = new_method
return cls
@add_method
class MyClass:
pass
obj = MyClass()
print(obj.new_method()) # 输出: 这是 MyClass 的新方法