python-带参数和不带参数的装饰器

5 阅读5分钟

一、不带参数的装饰器(两层嵌套)

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 内部可以使用 param1param2,也可以调用原始的 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 只能捕获 funcwrapper 可以捕获外层参数和 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

六、记忆口诀

  • 不带参数:两层包,@ 后直接跟名字,原函数做参数,返回内层替原身。
  • 带参数:三层套,@ 后先调外层拿中层,中层再收原函数,内层闭包用参数。