Python中的装饰器参数传递与多层装饰器
字数 1815 2025-11-18 21:45:58

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!

执行顺序解析:

  1. @repeat(num_times=3) 首先被求值。它调用 repeat(3)
  2. repeat(3) 返回内部定义的 decorator_repeat 函数(此时 num_times=3 已被记住)。
  3. 现在变成了 @decorator_repeat,它开始装饰 say_hello 函数:say_hello = decorator_repeat(say_hello)
  4. decorator_repeat(say_hello) 返回 wrapper 函数。
  5. 最终调用 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

执行顺序解析(核心):

  1. 装饰器的应用顺序:@decorator_one 应用在 @decorator_two 的结果之上。代码等价于:
    my_function = decorator_one(decorator_two(my_function))
  2. 调用 my_function() 时:
    • 首先调用 decorator_one 返回的 wrapper
    • 在这个 wrapper 中,func() 现在是 decorator_two(my_function) 返回的 wrapper
    • 所以执行 func() 会进入 decorator_twowrapper
    • decorator_twowrapper 中,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 装饰器。

掌握这些细节,你就能在各种复杂场景下自信地使用装饰器了。

Python中的装饰器参数传递与多层装饰器 装饰器是Python中一个强大且灵活的特性,它允许在不修改原函数代码的情况下,为函数添加新的功能。理解装饰器的参数传递和多层装饰器的执行顺序,是掌握装饰器高级用法的关键。 1. 装饰器基础回顾 首先,我们快速回顾一下最简单的装饰器(无参装饰器)是如何工作的。装饰器本质上是一个可调用对象(通常是函数或类),它接受一个函数作为参数,并返回一个新的函数。 输出: 这里, @simple_decorator 是语法糖,它等价于 say_hello = simple_decorator(say_hello) 。 2. 装饰器参数传递的三种场景 装饰器相关的参数传递可以分为三种情况,理解它们至关重要。 场景一:被装饰的函数带参数 上面的 say_hello 函数没有参数。如果被装饰的函数有参数,装饰器内部的 wrapper 函数需要能够接收这些参数并传递给原函数。 输出: 关键点: wrapper 函数使用 *args (接收位置参数)和 **kwargs (接收关键字参数)来通用地接收任何参数,并在调用 func 时将其解包传递。同时,它也会捕获并返回原函数的返回值。 场景二:装饰器本身带参数(创建装饰器工厂) 有时我们希望装饰器本身也能接受参数,以便在不同情况下定制装饰器的行为。这时,我们需要一个“装饰器工厂”——一个返回装饰器的函数。 输出: 执行顺序解析: @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次。 场景三:装饰器本身和被装饰的函数都带参数 这是前两种场景的结合。装饰器工厂接受自己的参数,同时它返回的装饰器要能处理被装饰函数的参数。 输出: 3. 多层装饰器的执行顺序与堆栈原理 当一个函数被多个装饰器装饰时,它们的应用顺序是从下往上(或从内到外)。理解这一点对于预测程序行为非常重要。 输出: 执行顺序解析(核心): 装饰器的应用顺序: @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 装饰器来保留这些信息。 总结 函数参数 :通过 wrapper(*args, **kwargs) 接收并传递。 装饰器参数 :需要创建装饰器工厂(外层函数接收参数,返回真正的装饰器)。 多层装饰器 :应用顺序从下往上,执行顺序像栈(先进后出)。 保留元信息 :使用 functools.wraps 装饰器。 掌握这些细节,你就能在各种复杂场景下自信地使用装饰器了。