1.装饰器是什么?用生活例子秒懂
想象你有一个普通的手机(就像一个基础函数):它能打电话、发消息(核心功能)。现在你想给它加个手机壳(防摔)、贴个膜(防刮)、挂个挂件(好看)—— 这些 "额外功能" 不会改变手机本身的打电话功能,但让它更实用了。
在 Python 中,装饰器就是给函数 "加装备" 的工具:它能在不修改函数原有代码的前提下,给函数添加新功能,例如:
日志记录:可以使用装饰器来记录函数的输入、输出或执行时间。
认证和授权:装饰器可以用于检查用户是否有权限执行特定操作。
缓存:装饰器可以缓存函数的结果,从而提高执行效率。
参数验证:可以使用装饰器来验证函数的输入参数是否符合预期。
代码注入:装饰器可以在函数的执行前后注入额外的代码。
举个最简单的例子:有一个打印 "你好" 的函数,我们用装饰器给它加个 "开场白" 和 "结束语",效果如下:
# 装饰器:给函数加前后提示
def add_tips(func):
def wrapper():
print("===== 开始 =====") # 新增功能
func() # 原函数功能
print("===== 结束 =====") # 新增功能
return wrapper
# 用装饰器"装饰"函数
@add_tips
def say_hello():
print("你好,装饰器!")
# 调用函数
say_hello()
'''
===== 开始 =====
你好,装饰器!
===== 结束 =====
'''
原函数say_hello
的代码没改,但多了前后提示
1.1 装饰器的 "底层逻辑":从闭包到语法糖
装饰器的本质是闭包的语法简化。先看一个不用@
符号的 "手动装饰" 过程,帮你理解原理:
# 1. 定义一个简单函数
def say_hi():
print("hi~")
# 2. 定义装饰器(闭包结构)
def my_decorator(func):
def wrapper():
print("装饰开始")
func()
print("装饰结束")
return wrapper
# 3. 手动用装饰器处理函数(等价于@my_decorator)
say_hi = my_decorator(say_hi)
# 4. 调用"装饰后"的函数
say_hi() # 输出:装饰开始 → hi~ → 装饰结束
核心结论:
@装饰器名
是 Python 的 "语法糖"(简化写法),等价于函数名 = 装饰器(函数名)
。- 装饰器的作用是 "替换" 原函数:调用原函数名时,实际执行的是装饰器里的
wrapper
函数。
1.2基础装饰器:给函数加固定功能
场景:给所有打印函数加 "分隔线",让输出更清晰。
def add_separator(func):
def wrapper():
print("-" * 20) # 新增:打印分隔线
func() # 执行原函数
print("-" * 20) # 新增:打印分隔线
return wrapper
@add_separator
def print_greeting():
print("欢迎使用本程序")
@add_separator
def print_farewell():
print("感谢使用,再见")
print_greeting()
# 输出:
# --------------------
# 欢迎使用本程序
# --------------------
print_farewell()
# 输出:
# --------------------
# 感谢使用,再见
# --------------------
解析:add_separator
装饰器像一个 "模板",可以复用给多个函数,避免重复写分隔线代码。
1.3带参数的装饰器:让功能更灵活
场景:希望控制函数重复执行的次数(比如重复打印 3 次、5 次)。
def repeat(times): # 外层:接收参数(重复次数)
def decorator(func): # 中层:接收函数
def wrapper(*args, **kwargs): # 内层:执行逻辑
for _ in range(times):
func(*args, **kwargs) # 带参数调用原函数
return wrapper
return decorator
@repeat(times=3) # 重复3次
def say(name):
print(f"你好,{name}!")
say("小明") # 输出:你好,小明!→ 你好,小明!→ 你好,小明!
关键细节:
- 带参数的装饰器需要三层函数:外层收参数、中层收函数、内层写执行逻辑。
*args
和**kwargs
用于接收原函数的参数(比如name
),确保装饰器适配任何参数的函数。
1.3 装饰器链:多个装饰器一起用
场景:先把文本转大写,再在末尾加感叹号(组合两个功能)。
# 装饰器1:转大写
def to_uppercase(func):
def wrapper(name):
result = func(name)
return result.upper() # 新增:转大写
return wrapper
# 装饰器2:加感叹号
def add_exclamation(func):
def wrapper(name):
result = func(name)
return result + "!" # 新增:加感叹号
return wrapper
# 装饰器链:先执行@to_uppercase,再执行@add_exclamation
@add_exclamation
@to_uppercase
def greet(name):
return f"你好,{name}"
print(greet("小红")) # 输出:你好,小红 → 转大写→ 你好,小红 → 加感叹号→ 你好,小红!
执行顺序秘诀:
- 装饰器链的执行顺序是 "从下到上装饰,从上到下执行"。
就像穿衣服:先穿内衣(@to_uppercase
),再穿外套(@add_exclamation
);脱衣服时先脱外套,再脱内衣。
1.4 类装饰器:用类实现装饰功能
class CountCalls:
def __init__(self, func):
self.func = func # 保存原函数
self.count = 0 # 记录调用次数(状态)
def __call__(self, *args, **kwargs):
self.count += 1 # 每次调用+1
print(f"这是第{self.count}次调用")
return self.func(*args, **kwargs) # 执行原函数
@CountCalls # 用类装饰器
def multiply(a, b):
return a * b
print(multiply(2, 3)) # 输出:这是第1次调用 → 6
print(multiply(4, 5)) # 输出:这是第2次调用 → 20
解析:
- 类装饰器需要实现
__init__
(接收原函数)和__call__
(定义装饰逻辑)方法。 - 优势:类可以保存状态(比如
count
变量),适合需要记录历史数据的场景。
1.5 实战场景:装饰器能帮你做什么?
1. 性能计时器:统计函数运行时间
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time() # 记录开始时间
result = func(*args, **kwargs) # 执行函数
end = time.time() # 记录结束时间
print(f"{func.__name__}运行耗时:{end - start:.2f}秒")
return result
return wrapper
@timer
def slow_task():
time.sleep(1.5) # 模拟耗时操作
slow_task() # 输出:slow_task运行耗时:1.50秒
2. 日志记录器:自动保存函数的输入输出
def log_io(func):
def wrapper(*args, **kwargs):
# 记录输入
print(f"函数{func.__name__}被调用,参数:{args},{kwargs}")
# 执行函数
result = func(*args, **kwargs)
# 记录输出
print(f"函数{func.__name__}返回结果:{result}")
return result
return wrapper
@log_io
def add(a, b):
return a + b
add(3, 5) # 输出:函数add被调用 → 参数:(3,5) → 返回结果:8
3. 权限验证:限制只有管理员能执行函数
def check_admin(func):
def wrapper(*args, **kwargs):
user = kwargs.get("user", "游客") # 获取用户名
if user != "admin":
raise PermissionError("权限不足!只有管理员可操作")
return func(*args, **kwargs)
return wrapper
@check_admin
def delete_data(user):
print("数据删除成功")
delete_data(user="admin") # 正常执行
delete_data(user="user") # 报错:PermissionError
4. 参数校验:确保函数接收有效参数
def validate_positive(func):
def wrapper(a, b):
if a <= 0 or b <= 0:
raise ValueError("参数必须是正数!")
return func(a, b)
return wrapper
@validate_positive
def divide(a, b):
return a / b
print(divide(8, 2)) # 输出:4.0
divide(-1, 5) # 报错:ValueError
5. 结果缓存:避免重复计算(提升效率)
def cache_result(func):
cache = {} # 用字典保存计算结果:参数→结果
def wrapper(n):
if n in cache:
print(f"从缓存获取{n}的结果")
return cache[n]
result = func(n)
cache[n] = result # 存入缓存
return result
return wrapper
@cache_result
def factorial(n):
print(f"计算{n}的阶乘")
if n == 1:
return 1
return n * factorial(n-1)
print(factorial(5)) # 输出:计算5→4→3→2→1的阶乘 → 120
print(factorial(5)) # 输出:从缓存获取5的结果 → 120(直接用缓存,不重复计算)
1.6初学者必踩的 3 个坑(附解决方案)
坑 1:装饰器导致函数名 "被替换"
@timer
def add(a, b):
return a + b
print(add.__name__) # 输出:wrapper(原函数名变成了wrapper)
解决:用functools.wraps
保留原函数信息
from functools import wraps
def timer(func):
@wraps(func) # 关键:复制原函数信息到wrapper
def wrapper(*args, **kwargs):
# 装饰逻辑
return wrapper
@timer
def add(a, b):
return a + b
print(add.__name__) # 输出:add(正确保留原函数名)
坑 2:带参数的装饰器少写一层函数
# 错误示例:带参数的装饰器必须有三层函数
def repeat(times):
def wrapper(): # 少了中间层,会报错
for _ in range(times):
func()
return wrapper
正确写法:三层函数结构(外→参数,中→函数,内→逻辑)
def repeat(times): # 1. 接收参数
def decorator(func): # 2. 接收函数
def wrapper(): # 3. 执行逻辑
for _ in range(times):
func()
return wrapper
return decorator
坑 3:装饰器链的执行顺序搞反
@decorator1
@decorator2
def func():
pass
记住:装饰顺序是decorator2
先装饰,decorator1
后装饰;执行时decorator1
的逻辑先执行,decorator2
的逻辑后执行(从外到内,就近原则)。
总结
装饰器的核心价值 装饰器是Python中"代码复用"的神器——它让你可以把日志、权限、计时等通用功能抽离出来,像"插件"一样灵活地给函数"赋能"。 学会装饰器后,你会发现代码变得更简洁、更易维护:核心业务逻辑和辅助功能分离,既方便扩展,又不破坏原有代码。下次看到`@`开头的语法,你就知道:这是给函数"戴装备"呢!