Python中的装饰器参数传递与多层装饰器执行顺序
字数 889 2025-11-10 20:11:39
Python中的装饰器参数传递与多层装饰器执行顺序
1. 问题描述
装饰器是Python中用于修改或增强函数行为的工具。但当装饰器本身需要参数,或多个装饰器同时修饰一个函数时,其执行顺序和参数传递可能令人困惑。例如:
@decorator1
@decorator2
def my_func():
pass
或带参数的装饰器:
@decorator_factory(arg1, arg2)
def my_func():
pass
面试常问:装饰器的嵌套顺序如何影响函数行为?带参数的装饰器如何实现?
2. 装饰器基础回顾
装饰器本质是接受函数作为参数的高阶函数。例如:
def simple_decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper
@simple_decorator
def greet():
print("Hello!")
# 调用 greet() 等价于 simple_decorator(greet)()
输出:
Before function call
Hello!
After function call
3. 带参数的装饰器
若装饰器需要接收外部参数(如@delay(seconds=2)),需构造一个装饰器工厂函数,返回真正的装饰器:
def repeat(n): # 装饰器工厂函数
def actual_decorator(func): # 真正的装饰器
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return actual_decorator
@repeat(n=3)
def say_hello():
print("Hello")
say_hello() # 打印3次 "Hello"
执行过程分析:
@repeat(n=3)先调用repeat(3),返回actual_decorator;actual_decorator(say_hello)返回wrapper函数;- 调用
say_hello()实际执行wrapper()。
4. 多层装饰器的执行顺序
当多个装饰器堆叠时,从下往上依次包装,但执行时从上往下调用:
def decorator1(func):
def wrapper1():
print("Decorator 1 before")
func()
print("Decorator 1 after")
return wrapper1
def decorator2(func):
def wrapper2():
print("Decorator 2 before")
func()
print("Decorator 2 after")
return wrapper2
@decorator1
@decorator2
def original():
print("Original function")
original()
输出结果:
Decorator 1 before
Decorator 2 before
Original function
Decorator 2 after
Decorator 1 after
原理分析:
- 装饰顺序:
original = decorator1(decorator2(original))- 先应用
@decorator2:将original包装成wrapper2; - 再应用
@decorator1:将wrapper2包装成wrapper1。
- 先应用
- 调用顺序:执行
wrapper1()→ 调用wrapper2()→ 调用original()。
5. 保留函数元信息
多层装饰可能导致函数名(__name__)、文档字符串等元信息丢失。解决方法:
- 使用
functools.wraps装饰内部包装函数:
from functools import wraps
def logged(func):
@wraps(func) # 将原函数的元信息复制到 wrapper
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
6. 总结关键点
- 带参装饰器:工厂函数返回装饰器,装饰器返回包装函数。
- 多层装饰器顺序:装饰时从下往上包装,执行时从上往下调用。
- 元信息保护:使用
functools.wraps避免属性丢失。 - 调试技巧:可通过打印语句或调试器观察包装链的构建过程。