Python装饰器是一种用于修改或扩展函数、类行为的设计模式,它本质上是一个接收函数作为参数并返回新函数的高阶函数,能够在不需要修改原函数代码的前提下,为函数添加额外的功能。这种特性让装饰器在日志记录、权限校验、性能统计等场景中应用十分广泛。

装饰器的核心原理
闭包是基础
要理解装饰器,首先需要掌握闭包的概念。闭包指的是在一个内部函数中,对外部作用域的变量进行引用,并且外部函数的返回值是内部函数。装饰器正是基于闭包实现的,外部函数接收被装饰的函数作为参数,内部函数则包装原函数的执行逻辑。
下面是一个简单的闭包示例:
def outer(func):
# 内部函数引用了外部函数的参数func
def inner():
print("执行前操作")
func()
print("执行后操作")
return inner
def test():
print("原函数执行")
# 将test函数传入outer,得到包装后的函数
wrapped = outer(test)
wrapped()
装饰器的语法糖
Python提供了@语法糖来简化装饰器的使用,不需要手动将函数传入装饰器再赋值,只需要在定义函数时加上@装饰器名即可。
上面的示例用语法糖改写后如下:
def outer(func):
def inner():
print("执行前操作")
func()
print("执行后操作")
return inner
@outer
def test():
print("原函数执行")
# 直接调用test,实际执行的是inner函数
test()
常见装饰器实战案例
无参数装饰器:日志记录
无参数装饰器是最基础的装饰器形式,适合为函数添加固定的额外功能,比如记录函数的调用日志。
import time
def log_decorator(func):
def wrapper():
start_time = time.time()
print(f"函数{func.__name__}开始执行,时间:{start_time}")
result = func()
end_time = time.time()
print(f"函数{func.__name__}执行结束,耗时:{end_time - start_time}秒")
return result
return wrapper
@log_decorator
def calculate():
total = 0
for i in range(1000000):
total += i
return total
calculate()
带参数装饰器:权限校验
如果装饰器需要接收参数,就需要在原有装饰器外层再包裹一层函数,用来接收装饰器的参数,再返回真正的装饰器。
def permission_check(required_role):
def decorator(func):
def wrapper(*args, **kwargs):
# 模拟当前用户角色
current_role = "admin"
if current_role != required_role:
raise PermissionError("当前用户没有执行该操作的权限")
return func(*args, **kwargs)
return wrapper
return decorator
@permission_check(required_role="admin")
def delete_user(user_id):
print(f"删除用户{user_id}成功")
# 当前角色是admin,可以正常执行
delete_user(1001)
# 修改装饰器参数为普通用户,会抛出异常
@permission_check(required_role="user")
def edit_user(user_id):
print(f"编辑用户{user_id}信息")
# edit_user(1001) # 执行该行会抛出权限异常
类装饰器
除了函数形式的装饰器,还可以使用类来实现装饰器,类装饰器需要定义__call__方法,让类的实例可以像函数一样被调用。
class CountCallDecorator:
def __init__(self, func):
self.func = func
self.call_count = 0
def __call__(self, *args, **kwargs):
self.call_count += 1
print(f"函数{self.func.__name__}已被调用{self.call_count}次")
return self.func(*args, **kwargs)
@CountCallDecorator
def greet(name):
print(f"你好,{name}")
greet("张三")
greet("李四")
greet("王五")
装饰器的注意事项
- 装饰器会改变原函数的
__name__、__doc__等属性,如果需要保留原函数的属性,可以使用functools.wraps装饰内部包装函数。 - 多个装饰器可以同时装饰一个函数,执行顺序是从下往上,也就是离函数定义最近的装饰器先执行。
- 装饰器的逻辑尽量保持简洁,避免添加过于复杂的业务逻辑,否则会增加代码的维护难度。
掌握装饰器的核心原理和常见用法后,就可以在实际开发中灵活运用,减少重复代码,让程序结构更加清晰易维护。