在 Python 中,有一个非常强大的特性——装饰器(Decorator)。它能在不修改函数源代码的前提下,动态地为函数增加功能,比如打印日志、验证权限、计时统计等。这种"无侵入式"增强功能的方式,让代码更灵活、更易维护。今天我们就来深入理解装饰器的工作原理和用法。 通俗版:
Def xx1(被装饰函数):
def xx2(如果被装饰函数有参数那么输入):
xxxxxxxxxxxxxxx
被装饰函数(如果被装饰函数有参数那么输入)
xxxxxxxxxxxxxxx
# 如果被装饰函数中含有reture则需要返回被装饰函数
# 没有则不需要
reture xx2
文章目录
-
- 一、 为什么需要装饰器?
-
- 🧪 装饰器基础:从函数赋值说起
- 二、 定义第一个装饰器:日志增强
-
- 1. 基础装饰器结构
- 2. 使用装饰器:@语法糖
- 3. 原理拆解:
- 三、 进阶:带参数的装饰器
-
- 1. 定义带参数的装饰器
- 2. 使用方式
- 3. 调用效果
- 四、 注意:函数身份信息的丢失
- 五、 核心知识小结
- 六、 练习
-
- 🧩 练习1:实现一个计时装饰器
- 🧩 练习2:实现兼容两种形式的装饰器
- 七、 总结
一、 为什么需要装饰器?
假设我们有一个打印日期的函数:
def now():
print('2024-6-1')
现在需要给它加一个功能:调用前自动打印日志(比如“开始调用 now()”)。如果直接修改 now() 的代码,会有两个问题:
- 多个函数需要加日志时,重复代码会非常繁琐;
- 破坏原函数的封装性,后续维护难度增加。
装饰器的出现完美解决了这个问题:在不修改原函数代码的前提下,动态增强函数功能。
🧪 装饰器基础:从函数赋值说起
要理解装饰器,先看一个简单的函数特性:函数可以被赋值给变量,通过变量调用。
def now():
print('2024-6-1')
f = now # 将函数赋值给变量(注意没有括号)
f() # 调用变量,等价于 now() → 输出:2024-6-1
print(f.__name__) # 函数对象的__name__属性 → 输出:now
这一特性是装饰器的基础——装饰器本质是一个接收函数、返回新函数的高阶函数。
二、 定义第一个装饰器:日志增强
装饰器本质是一个返回函数的高阶函数。它接收原函数作为参数,返回一个包装后的新函数,新函数会在调用原函数的前后添加额外逻辑。
1. 基础装饰器结构
def log(func):
# 定义包装函数wrapper,用于增强原函数
def wrapper(*args, **kw):
# 调用原函数前:添加日志功能
print(f'call {func.__name__}():')
# 调用原函数,并返回结果
return func(*args, **kw)
# 返回包装后的函数
return wrapper
2. 使用装饰器:@语法糖
用 @log 标记需要增强的函数,等价于 now = log(now):
@log # 等价于 now = log(now)
def now():
print('2024-6-1')
now()
# 输出:
# call now(): # 装饰器新增的日志
# 2024-6-1 # 原函数的功能
3. 原理拆解:
wrapper(*args, **kw) 中的 *args, **kw 是为了接收原函数的任意参数,保证无论原函数有多少参数,装饰器都能兼容。
三、 进阶:带参数的装饰器
如果需要自定义日志内容(比如打印“执行”或“调用”),装饰器需要三层嵌套:最外层接收参数,中间层接收原函数,内层是包装函数。
1. 定义带参数的装饰器
def log(text): # 最外层:接收装饰器参数
def decorator(func): # 中间层:接收原函数
def wrapper(*args, **kw): # 内层:包装函数
print(f'{text} {func.__name__}():') # 使用参数text
return func(*args, **kw)
return wrapper # 返回包装函数
return decorator # 返回中间层函数
通俗版:
def log(text): # 外层:接收装饰器的参数,比如 'execute'
def 真正装饰器(被装饰的函数): # 中层:接收被装饰的函数
def 包装函数(*args, **kwargs): # 内层:接收函数运行时的参数
print(f'{text} {被装饰的函数.__name__}():') # 打印日志信息
return 被装饰的函数(*args, **kwargs) # 调用原函数,并返回结果(如果原函数有返回值)
return 包装函数 # 中层返回包装后的函数
return 真正装饰器 # 外层返回装饰器
2. 使用方式
@log('execute') # 等价于 now = log('execute')(now)
def now():
print('2024-6-1')
3. 调用效果
now()
# 输出:
# execute now(): # 自定义的日志文本
# 2024-6-1
四、 注意:函数身份信息的丢失
装饰器会改变原函数的元信息(如 __name__ 属性):
print(now.__name__) # 输出:wrapper(而非原函数名now)
这是因为 now 已指向 wrapper 函数。但有些代码依赖函数的原始名称(比如序列化、调试工具),这会导致问题。解决方法是用 functools.wraps 复制原函数的元信息。
✅ 正确写法:保留原函数信息
import functools
def log(func):
@functools.wraps(func) # 复制原函数元信息到wrapper
def wrapper(*args, **kw):
print(f'call {func.__name__}():')
return func(*args, **kw)
return wrapper
@log
def now():
print('2024-6-1')
print(now.__name__) # 输出:now(正确保留原函数名)
带参数的装饰器也需要同样处理:
import functools
def log(text):
def decorator(func):
@functools.wraps(func) # 关键:复制元信息
def wrapper(*args, **kw):
print(f'{text} {func.__name__}():')
return func(*args, **kw)
return wrapper
return decorator
五、 核心知识小结
装饰器本质 | 返回函数的高阶函数 | def log(func): return wrapper |
@语法糖 | 等价于 函数 = 装饰器(函数) | @log → now = log(now) |
带参数的装饰器 | 三层嵌套,最外层接收参数 | @log('execute') |
functools.wraps | 保留原函数的__name__、文档等信息 | @functools.wraps(func) |
wrapper参数设计 | 用*args, **kw兼容任意函数参数 | def wrapper(*args, **kw): |
六、 练习
🧩 练习1:实现一个计时装饰器
尝试写一个装饰器,统计函数的执行时间:
import time
import functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kw):
start = time.time()
result = func(*args, **kw) # 调用原函数
end = time.time()
print(f'{func.__name__} 执行时间:{end – start:.2f}秒')
return result
return wrapper
# 测试
@timer
def slow_func():
time.sleep(1) # 模拟耗时操作
slow_func() # 输出:slow_func 执行时间:1.00秒
🧩 练习2:实现兼容两种形式的装饰器
需求:编写一个 log 装饰器,既能支持 @log(不带参数),又能支持 @log('execute')(带参数)。
分析:
- 不带参数时,装饰器直接接收被装饰的函数(可调用对象);
- 带参数时,装饰器先接收参数(不可调用对象,如字符串),再返回处理函数的逻辑。
可通过 callable() 函数判断参数类型,区分两种场景:
import functools
def log(arg):
# 场景1:@log 形式(arg是被装饰的函数)
if callable(arg):
@functools.wraps(arg)
def wrapper(*args, **kwargs):
print(f'Call {arg.__name__}()') # 默认日志
return arg(*args, **kwargs)
return wrapper
# 场景2:@log('xxx') 形式(arg是装饰器参数)
else:
def decorator(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
print(f'{arg} {fn.__name__}()') # 自定义日志
return fn(*args, **kwargs)
return wrapper
return decorator
测试代码:
# 测试不带参数
@log
def f1():
print("执行 f1")
f1()
# 输出:Call f1() \\n 执行 f1
# 测试带参数
@log('execute')
def f2():
print("执行 f2")
f2()
# 输出:execute f2() \\n 执行 f2
原理:
- callable(arg) 判断 arg 是否为可调用对象(函数):
- 是 → 进入 if 分支,直接处理被装饰函数;
- 否 → 进入 else 分支,先接收参数,再返回处理函数的中间逻辑。
七、 总结
装饰器是 Python 函数式编程的精髓,它让代码更灵活、更易维护。核心要点:
合理使用装饰器,可以让代码更简洁、更灵活,同时避免重复逻辑。
评论前必须登录!
注册