装饰器
装饰器是Python最强大的特性之一,它允许我们在不修改原函数代码的情况下,增强函数的功能,实现代码复用和横切关注点分离。
什么是装饰器?
装饰器是一个接收函数作为参数,并返回新函数的函数。
┌─────────────────────────────────────────────────────────────────────┐ │ 装饰器原理图解 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 装饰器本质上是一个高阶函数: │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ decorator(func) ──▶ new_func │ │ │ │ │ │ │ │ 输入: 原函数 │ │ │ │ 输出: 包装后的新函数 │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ 常见用途: │ │ • 日志记录 │ │ • 性能计时 │ │ • 权限检查 │ │ • 输入验证 │ │ • 缓存结果 │ │ │ └─────────────────────────────────────────────────────────────────────┘
Python
# 不使用装饰器:手动包装
def say_hello():
print("Hello!")
# 包装函数
def log_decorator(func):
def wrapper():
print("开始调用函数...")
func()
print("函数调用完成")
return wrapper
# 手动装饰
logged_hello = log_decorator(say_hello)
logged_hello()
# 输出:
# 开始调用函数...
# Hello!
# 函数调用完成
@ 语法糖
使用 @ 符号可以更简洁地应用装饰器。
┌─────────────────────────────────────────────────────────────────────┐ │ @ 语法糖原理 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ @decorator │ │ def func(): │ │ pass │ │ │ │ 等价于: │ │ │ │ def func(): │ │ pass │ │ func = decorator(func) │ │ │ │ 执行顺序: │ │ │ │ 1. 定义 func (原函数) │ │ 2. 调用 decorator(func),传入原函数 │ │ 3. 返回新函数 │ │ 4. 将新函数赋值给 func │ │ │ └─────────────────────────────────────────────────────────────────────┘
Python
def log_decorator(func):
def wrapper():
print("开始调用函数...")
func()
print("函数调用完成")
return wrapper
# 使用 @ 语法糖
@log_decorator
def say_hello():
print("Hello!")
say_hello()
# 输出:
# 开始调用函数...
# Hello!
# 函数调用完成
保留原函数元数据
装饰后的函数会丢失原函数的 __name__、__doc__ 等元数据。使用 functools.wraps 解决这个问题。
┌─────────────────────────────────────────────────────────────────────┐ │ functools.wraps 作用 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 不使用 wraps: │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ @decorator │ │ │ │ def add(a, b): │ │ │ │ """返回两个数的和""" │ │ │ │ return a + b │ │ │ │ │ │ │ │ add.__name__ ──▶ "wrapper" ❌ │ │ │ │ add.__doc__ ──▶ None ❌ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ 使用 wraps: │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ @wraps(func) ◀── 关键一步 │ │ │ │ def wrapper(*args, **kwargs): │ │ │ │ ... │ │ │ │ │ │ │ │ add.__name__ ──▶ "add" ✓ │ │ │ │ add.__doc__ ──▶ "返回两个数的和" ✓ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘
Python
from functools import wraps
# 不使用 wraps
def bad_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@bad_decorator
def add(a, b):
"""返回两个数的和"""
return a + b
print(add.__name__) # wrapper - 元数据丢失!
print(add.__doc__) # None
# 使用 wraps 保留元数据
def good_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@good_decorator
def add2(a, b):
"""返回两个数的和"""
return a + b
print(add2.__name__) # add2 - 元数据保留!
print(add2.__doc__) # 返回两个数的和
带参数的装饰器
装饰器工厂(三层嵌套)允许装饰器接受参数。
┌─────────────────────────────────────────────────────────────────────┐ │ 三层嵌套装饰器工厂 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ def decorator_factory(arg): ← 第1层: 接收装饰器参数 │ │ def decorator(func): ← 第2层: 接收被装饰的函数 │ │ def wrapper(...): ← 第3层: 包装函数 │ │ ... │ │ return wrapper │ │ return decorator │ │ │ │ 调用链: │ │ @decorator_factory(arg) ──▶ 返回 decorator 函数 │ │ @返回的decorator ──▶ 装饰目标函数 │ │ │ └─────────────────────────────────────────────────────────────────────┘
Python
from functools import wraps
# 装饰器工厂: repeat(times)
def repeat(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
results = []
for _ in range(times):
result = func(*args, **kwargs)
results.append(result)
return results
return wrapper
return decorator
@repeat(3)
def greet(name):
return f"你好,{name}!"
print(greet("张三"))
# ['你好,张三!', '你好,张三!', '你好,张三!']
多层装饰器
同一个函数可以使用多个装饰器,按从近到远的顺序执行。
┌─────────────────────────────────────────────────────────────────────┐ │ 多层装饰器执行顺序 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ @decorator1 │ │ @decorator2 │ │ def func(): │ │ pass │ │ │ │ 等价于: │ │ func = decorator1(decorator2(func)) │ │ │ │ 执行顺序: │ │ │ │ func() 调用 │ │ │ │ │ ├── decorator1.wrapper 开始 │ │ │ │ │ │ │ ├── decorator2.wrapper 开始 │ │ │ │ │ │ │ │ │ ├── 原函数 func 执行 │ │ │ │ │ │ │ │ │ └── decorator2.wrapper 结束 │ │ │ │ │ │ │ └── decorator1.wrapper 结束 │ │ │ │ │ 结论: decorator2 先执行,decorator1 后执行 │ │ │ └─────────────────────────────────────────────────────────────────────┘
Python
from functools import wraps
def decorator1(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("decorator1 开始")
result = func(*args, **kwargs)
print("decorator1 结束")
return result
return wrapper
def decorator2(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("decorator2 开始")
result = func(*args, **kwargs)
print("decorator2 结束")
return result
return wrapper
@decorator1
@decorator2
def say_hello():
print("Hello!")
return "done"
print(say_hello())
# 输出:
# decorator1 开始
# decorator2 开始
# Hello!
# decorator2 结束
# decorator1 结束
# done
常用内置装饰器
@property - 将方法变为属性
Python
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return 3.14159 * self.radius ** 2
c = Circle(5)
print(c.area) # 78.53975 (像访问属性一样)
# c.area = 100 # AttributeError: property 'area' has no setter
@staticmethod 和 @classmethod
Python
class Math:
@staticmethod
def add(a, b):
return a + b
@classmethod
def multiply(cls, a, b):
return a * b
# 静态方法可以直接用类名调用,不需要实例化
print(Math.add(3, 4)) # 7
# 类方法接收类作为第一个参数
print(Math.multiply(3, 4)) # 12
类装饰器
装饰器不仅可以是函数,也可以是类。
Python
class CountCalls:
def __init__(self, func):
self.func = func
self.call_count = 0
@wraps(func)
def __call__(self, *args, **kwargs):
self.call_count += 1
print(f"函数被调用了 {self.call_count} 次")
return self.func(*args, **kwargs)
@CountCalls
def say_hi():
print("Hi!")
say_hi() # 函数被调用了 1 次\nHi!
say_hi() # 函数被调用了 2 次\nHi!
章节测验
问题1: 装饰器的本质是什么?
问题2: @wraps 的作用是什么?
问题3: 多层装饰器的执行顺序是?
问题4: 带参数的装饰器需要几层嵌套?
练习题
练习1: 计时装饰器
创建一个装饰器,记录函数执行时间。
练习2: 缓存装饰器
创建一个装饰器,缓存函数结果(使用字典)。
练习3: 验证装饰器
创建一个装饰器,验证函数参数是否为正数。
🐍 Python 在线代码编辑器
📝 运行结果