Python中的装饰器参数传递与多层装饰器
装饰器是Python中一个强大且灵活的特性,它允许在不修改原函数代码的情况下,为函数添加新的功能。理解装饰器的参数传递和多层装饰器的执行顺序,是掌握装饰器高级用法的关键。
1. 装饰器基础回顾
首先,我们快速回顾一下最简单的装饰器(无参装饰器)是如何工作的。装饰器本质上是一个可调用对象(通常是函数或类),它接受一个函数作为参数,并返回一个新的函数。
def simple_decorator(func): # 接受一个函数作为参数
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper # 返回一个新的函数(wrapper)
@simple_decorator
def say_hello():
print("Hello!")
# 调用被装饰后的函数
say_hello()
输出:
Something is happening before the function is called.
Hello!
Something is happening after the function is called.
这里,@simple_decorator 是语法糖,它等价于 say_hello = simple_decorator(say_hello)。
2. 装饰器参数传递的三种场景
装饰器相关的参数传递可以分为三种情况,理解它们至关重要。
场景一:被装饰的函数带参数
上面的 say_hello 函数没有参数。如果被装饰的函数有参数,装饰器内部的 wrapper 函数需要能够接收这些参数并传递给原函数。
def decorator_with_func_args(func):
def wrapper(*args, **kwargs): # 使用 *args 和 **kwargs 接收任意参数
print("Before call")
result = func(*args, **kwargs) # 将参数原封不动地传给原函数
print("After call")
return result # 返回原函数的执行结果
return wrapper
@decorator_with_func_args
def greet(name, greeting="Hi"):
print(f"{greeting}, {name}!")
return "Finished"
# 调用
result = greet("Alice", greeting="Hello")
print(f"Function returned: {result}")
输出:
Before call
Hello, Alice!
After call
Function returned: Finished
关键点:wrapper 函数使用 *args(接收位置参数)和 **kwargs(接收关键字参数)来通用地接收任何参数,并在调用 func 时将其解包传递。同时,它也会捕获并返回原函数的返回值。
场景二:装饰器本身带参数(创建装饰器工厂)
有时我们希望装饰器本身也能接受参数,以便在不同情况下定制装饰器的行为。这时,我们需要一个“装饰器工厂”——一个返回装饰器的函数。
def repeat(num_times): # 这是一个装饰器工厂,它接受参数
def decorator_repeat(func): # 这才是真正的装饰器,它接受一个函数
def wrapper(*args, **kwargs):
for _ in range(num_times): # 使用装饰器工厂的参数 num_times
result = func(*args, **kwargs)
return result # 通常返回最后一次调用的结果
return wrapper
return decorator_repeat # 装饰器工厂返回一个装饰器
@repeat(num_times=3) # 这里调用装饰器工厂,返回一个装饰器,然后装饰函数
def say_hello():
print("Hello!")
say_hello()
输出:
Hello!
Hello!
Hello!
执行顺序解析:
@repeat(num_times=3)首先被求值。它调用repeat(3)。repeat(3)返回内部定义的decorator_repeat函数(此时num_times=3已被记住)。- 现在变成了
@decorator_repeat,它开始装饰say_hello函数:say_hello = decorator_repeat(say_hello)。 decorator_repeat(say_hello)返回wrapper函数。- 最终调用
say_hello()时,实际上调用的是wrapper(),它内部会执行原函数3次。
场景三:装饰器本身和被装饰的函数都带参数
这是前两种场景的结合。装饰器工厂接受自己的参数,同时它返回的装饰器要能处理被装饰函数的参数。
def log_decorator(prefix="[LOG]"): # 装饰器工厂参数
def actual_decorator(func): # 装饰器
def wrapper(*args, **kwargs): # 处理被装饰函数的参数
print(f"{prefix} Calling function: {func.__name__}")
result = func(*args, **kwargs)
print(f"{prefix} Finished call: {func.__name__}")
return result
return wrapper
return actual_decorator
@log_decorator(prefix="***") # 带参数的装饰器
def add(a, b): # 被装饰的函数也带参数
return a + b
print(add(5, 3))
输出:
*** Calling function: add
*** Finished call: add
8
3. 多层装饰器的执行顺序与堆栈原理
当一个函数被多个装饰器装饰时,它们的应用顺序是从下往上(或从内到外)。理解这一点对于预测程序行为非常重要。
def decorator_one(func):
def wrapper():
print("Decorator 1 - Before")
func()
print("Decorator 1 - After")
return wrapper
def decorator_two(func):
def wrapper():
print("Decorator 2 - Before")
func()
print("Decorator 2 - After")
return wrapper
@decorator_one
@decorator_two
def my_function():
print("Original function")
my_function()
输出:
Decorator 1 - Before
Decorator 2 - Before
Original function
Decorator 2 - After
Decorator 1 - After
执行顺序解析(核心):
- 装饰器的应用顺序:
@decorator_one应用在@decorator_two的结果之上。代码等价于:
my_function = decorator_one(decorator_two(my_function)) - 调用
my_function()时:- 首先调用
decorator_one返回的wrapper。 - 在这个
wrapper中,func()现在是decorator_two(my_function)返回的wrapper。 - 所以执行
func()会进入decorator_two的wrapper。 - 在
decorator_two的wrapper中,func()才是原始的my_function。 - 因此执行顺序是:装饰器1前 -> 装饰器2前 -> 原函数 -> 装饰器2后 -> 装饰器1后。
- 首先调用
可以把多层装饰器想象成一个洋葱,从外到内一层层包裹,执行时从最外层开始剥,直到核心(原函数),然后再从内到外返回。
4. 保留函数元信息
使用装饰器后,原函数的元信息(如 __name__、__doc__)会被包装函数的元信息覆盖。可以使用 functools.wraps 装饰器来保留这些信息。
import functools
def my_decorator(func):
@functools.wraps(func) # 将原函数的元信息复制到wrapper函数
def wrapper(*args, **kwargs):
"""Wrapper function docstring."""
print("Before call")
return func(*args, **kwargs)
return wrapper
@my_decorator
def example():
"""Example function docstring."""
pass
print(example.__name__) # 输出 'example',而不是 'wrapper'
print(example.__doc__) # 输出 'Example function docstring.',而不是 'Wrapper function docstring.'
总结
- 函数参数:通过
wrapper(*args, **kwargs)接收并传递。 - 装饰器参数:需要创建装饰器工厂(外层函数接收参数,返回真正的装饰器)。
- 多层装饰器:应用顺序从下往上,执行顺序像栈(先进后出)。
- 保留元信息:使用
functools.wraps装饰器。
掌握这些细节,你就能在各种复杂场景下自信地使用装饰器了。