装饰器
理解闭包与装饰器的底层原理
掌握函数装饰器的编写与使用
能实现带参数的装饰器,了解类装饰器
核心讲解
装饰器是 Python 的高级语法,本质是不修改原函数的代码,动态给函数扩展功能,符合开闭原则,是 Pythonic 代码的典型代表。
(1)前置知识:闭包
闭包:内层函数引用外层函数的变量,外层函数返回内层函数。闭包是装饰器的基础。
特点:内层函数可以记住外层函数的环境,即使外层函数执行结束
作用:封装变量,实现数据持久化
(2)装饰器原理
装饰器本质是一个函数,接收原函数作为参数,返回一个包装后的新函数。调用原函数时,实际执行的是包装后的函数,从而在不修改原代码的情况下增加功能。
语法糖:@装饰器名 写在函数定义上面,等价于 函数 = 装饰器(函数)
(3)装饰器的执行流程
程序加载时,执行装饰器函数,把原函数传进去,返回包装函数
调用原函数时,实际调用的是包装函数
包装函数里执行前置逻辑 → 调用原函数 → 执行后置逻辑 → 返回结果
(4)进阶:带参数的装饰器
装饰器本身需要接收参数时,要再套一层函数,最外层接收参数,中间层接收原函数,最内层是包装函数。
代码示例
import time
from functools import wraps
# ===== 1. 基础装饰器 =====
# 装饰器函数:计时功能
def timer(func):
# wraps保留原函数的元信息(函数名、文档等)
@wraps(func)
def wrapper(*args, **kwargs):
# 前置逻辑
start = time.time()
print(f"函数 {func.__name__} 开始执行")
# 调用原函数,接收参数,返回结果
result = func(*args, **kwargs)
# 后置逻辑
end = time.time()
print(f"函数 {func.__name__} 执行完毕,耗时:{end-start:.4f}秒")
return result
return wrapper
# 使用装饰器
@timer
def add(a, b):
"""两个数相加"""
time.sleep(1)
return a + b
# 调用函数
result = add(10, 20)
print("结果:", result)
print("函数名:", add.__name__) # 加了wraps保留原函数名
# ===== 2. 日志装饰器 =====
def logger(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"[INFO] 调用函数 {func.__name__},参数:{args} {kwargs}")
try:
result = func(*args, **kwargs)
print(f"[INFO] 函数 {func.__name__} 执行成功")
return result
except Exception as e:
print(f"[ERROR] 函数 {func.__name__} 执行失败:{e}")
raise
return wrapper
@logger
def divide(a, b):
return a / b
divide(10, 2)
# divide(10, 0) # 触发错误日志
# ===== 3. 带参数的装饰器 =====
def repeat(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def say_hello(name):
print(f"Hello {name}")
say_hello("Python") # 会打印3次
# ===== 4. 多个装饰器叠加 =====
@timer
@logger
def test_func():
time.sleep(0.5)
print("测试函数执行")
test_func()
常见误区
忘记加括号:装饰器函数名后面不要加括号,带参数的装饰器才加括号
原函数元信息丢失:不用
@wraps(func)的话,函数名、文档字符串会变成 wrapper 的,建议都加上参数传递错误:包装函数要用
*args, **kwargs接收任意参数,原函数有参数才不会报错带参数装饰器少一层:带参数的装饰器要三层函数,少一层会出错
装饰器执行时机:装饰器在函数定义时就执行了,不是调用时才执行
课后练习
编写一个权限检查装饰器,只有用户是 admin 时才执行函数
编写一个缓存装饰器,相同参数调用函数时直接返回缓存结果,不重复执行
编写一个带参数的装饰器,可以指定异常类型,捕获对应异常并返回默认值
思考:装饰器和继承相比,有什么优势?

