一、不带参数的装饰器(两层嵌套)
1. 基本结构
def decorator(func):
def wrapper(*args, **kwargs):
# 前置操作
result = func(*args, **kwargs)
# 后置操作
return result
return wrapper
2. 使用语法
@decorator
def say_hello():
print("hello")
3. 执行步骤拆解
第 1 步:Python 解释器解析 @decorator
- 看到
@decorator,它不是一个函数调用,而是一个装饰器引用。 - 此时要求
decorator是一个可调用对象(通常是一个函数),并且它只接受一个参数(即下面的被装饰函数)。
第 2 步:将被装饰函数 say_hello 作为参数传递给 decorator
- 相当于执行:
decorator(say_hello) - 进入
decorator函数体:- 形参
func指向原始的say_hello函数对象。 - 在
decorator内部定义一个新函数wrapper(此时wrapper还没有被执行,只是被定义)。 wrapper内部会调用func(即原始的say_hello)。decorator返回wrapper函数对象。
- 形参
第 3 步:替换函数名
- 原来
say_hello这个名字,现在被重新绑定到decorator的返回值,也就是wrapper函数。 - 等效代码:
say_hello = decorator(say_hello)
第 4 步:调用被装饰后的函数
- 当你写
say_hello()时,实际执行的是wrapper()函数。 wrapper内部先执行前置代码,然后调用原始的func(即原来的say_hello),再执行后置代码,最后返回结果。
4. 调用时序图(不带参数)
定义阶段:
def decorator(func): # 1. 定义装饰器函数
def wrapper(*args,**kwargs):
...
return wrapper
@decorator # 2. 装饰器生效
def say_hello(): ... # 3. 定义原函数
运行时:
say_hello() # 4. 实际调用 wrapper()
-> wrapper() 内:
-> 前置操作
-> 原 say_hello()
-> 后置操作
二、带参数的装饰器(三层嵌套)
1. 基本结构
def decorator_with_args(param1, param2):
def actual_decorator(func):
def wrapper(*args, **kwargs):
# 可以使用 param1, param2
result = func(*args, **kwargs)
# 也可以使用 param1, param2
return result
return wrapper
return actual_decorator
2. 使用语法
@decorator_with_args('hello', 42)
def say_hello():
print("hello")
3. 执行步骤拆解(非常重要,分清楚三层)
第 1 步:Python 解析 @decorator_with_args('hello', 42)
- 注意这里有括号,表示立即调用
decorator_with_args函数,并传入参数'hello'和42。 - 所以
@后面跟的是一个函数调用表达式,而不是简单的函数名。
第 2 步:执行外层函数 decorator_with_args('hello', 42)
- 进入
decorator_with_args函数体:- 形参
param1='hello',param2=42 - 在函数内部定义一个内层函数
actual_decorator(它只接受一个func参数)。 decorator_with_args返回actual_decorator函数对象。
- 形参
此时,@ 后面的表达式计算结果是一个函数 actual_decorator。
等效于:temp_decorator = decorator_with_args('hello', 42)
第 3 步:将被装饰函数 say_hello 传给 actual_decorator
- 现在
@actual_decorator(注意这里 actual_decorator 是上一步返回的)会执行:actual_decorator(say_hello) - 进入
actual_decorator函数体:- 形参
func指向原始的say_hello - 在内部定义
wrapper函数(它可以访问外层的param1,param2,因为闭包)。 actual_decorator返回wrapper函数对象。
- 形参
第 4 步:替换函数名
say_hello = actual_decorator(say_hello)
实际上等价于:say_hello = decorator_with_args('hello',42)(say_hello)- 现在
say_hello指向wrapper。
第 5 步:调用被装饰后的函数
say_hello()→ 执行wrapper()wrapper内部可以使用param1和param2,也可以调用原始的func(原say_hello)。
4. 调用时序图(带参数)
定义阶段:
def decorator_with_args(p1,p2): # 1. 定义外层函数
def actual_decorator(func): # 2. 定义中层函数(真正的装饰器)
def wrapper(*args,**kwargs):
...
return wrapper
return actual_decorator
运行时(装饰发生时):
@decorator_with_args('hello',42) # 3. 立即调用外层函数,返回 actual_decorator
def say_hello(): ... # 4. 定义原函数
# 上面两步等效于:
# actual = decorator_with_args('hello',42) # 步骤 A
# say_hello = actual(say_hello) # 步骤 B
实际调用时:
say_hello() # 5. 调用 wrapper
-> wrapper() 内:
-> 可以使用 'hello',42
-> 原 say_hello()
三、带参数装饰器的特殊情况:@decorator()(空括号)
def decorator_with_default(func=None):
def actual_decorator(func):
def wrapper(*args,**kwargs):
...
return wrapper
if func is None:
return actual_decorator
else:
return actual_decorator(func)
这种写法允许 @decorator() 和 @decorator 两种用法,但本质仍然是三层结构,只是通过参数默认值做了兼容。不过这是高级技巧,笔记中可以作为延伸。
四、核心区别总结表(用于笔记)
| 维度 | 不带参数 @deco | 带参数 @deco(arg) |
|---|---|---|
| 嵌套层数 | 2 层(deco → wrapper) | 3 层(deco → actual_deco → wrapper) |
@ 后的表达式 | 函数对象 | 函数调用(立即执行) |
| 外层函数何时执行 | 在 @ 解析时不调用,只是引用 | 在 @ 解析时立即调用,并传入参数 |
| 谁接收被装饰函数 | deco 直接接收 | actual_deco(外层函数的返回值)接收 |
| 闭包捕获 | wrapper 只能捕获 func | wrapper 可以捕获外层参数和 func |
| 灵活性 | 行为固定 | 可配置(如重试次数、日志级别) |
| 等价代码 | say_hello = deco(say_hello) | say_hello = deco(arg)(say_hello) |
五、完整示例(带详细注释)
不带参数:简单计时器
import time
def timer(func): # 第1层:接收被装饰函数
def wrapper(*args, **kwargs): # 第2层:包裹函数
start = time.time()
result = func(*args, **kwargs)
print(f"耗时 {time.time()-start:.2f}s")
return result
return wrapper # 返回 wrapper,替换原函数
@timer
def sleep_one():
time.sleep(1)
# 调用过程:
# 1. @timer → timer(sleep_one) → 返回 wrapper
# 2. sleep_one 现在等于 wrapper
# 3. sleep_one() → 执行 wrapper
带参数:可指定重复次数的装饰器
def repeat(times): # 第1层:接收装饰器参数
print(f"外层函数执行,times={times}")
def decorator(func): # 第2层:接收被装饰函数(真正的装饰器)
print(f"中层函数执行,func={func.__name__}")
def wrapper(*args, **kwargs):# 第3层:包裹函数
print(f"wrapper 执行,准备重复 {times} 次")
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator # 返回中层函数
@repeat(times=3) # 1. 立即调用 repeat(3) → 返回 decorator
def greet(name): # 2. 此时 @decorator 生效 → decorator(greet) → 返回 wrapper
print(f"Hello {name}")
# 调用:
greet("Alice")
# 输出:
# 外层函数执行,times=3
# 中层函数执行,func=greet
# wrapper 执行,准备重复 3 次
# Hello Alice
# Hello Alice
# Hello Alice
六、记忆口诀
- 不带参数:两层包,
@后直接跟名字,原函数做参数,返回内层替原身。 - 带参数:三层套,
@后先调外层拿中层,中层再收原函数,内层闭包用参数。